在 Java7 的ForkJoinPool
类中,有一条关于实现的注释,其中指出:
方法 signalWork() 和 scan() 是主要的瓶颈,因此特别是微优化/损坏。有很多内联赋值(形式为“while ((local = field) != 0)”),这通常是确保所需读取顺序(有时很关键)的最简单方法
我的问题是:内联赋值如何帮助读取顺序(我熟悉 Java 内存模型,但我看不到内联赋值在这里有什么帮助)?
在 Java7 的ForkJoinPool
类中,有一条关于实现的注释,其中指出:
方法 signalWork() 和 scan() 是主要的瓶颈,因此特别是微优化/损坏。有很多内联赋值(形式为“while ((local = field) != 0)”),这通常是确保所需读取顺序(有时很关键)的最简单方法
我的问题是:内联赋值如何帮助读取顺序(我熟悉 Java 内存模型,但我看不到内联赋值在这里有什么帮助)?
理论上,内联应该对排序没有影响。编译器可以自由地重新排序您的代码,JIT 编译器和某些情况下的 CPU 也是如此。
阅读了相关代码后,您应该注意在上述 while 循环中读取的许多字段都是volatile的事实。易失性读取和写入不能重新排序,并且受happens-before关系的影响。有关 volatile 语义的出色解释,请参阅此博客文章。
通过内联易失性读取,其余条件受可见性规则的约束,不符合重新排序的条件。这可能很难通过其他方式实现。
我认为 ninjalj 是正确的,因为表达式可以安全地重写为local = field; while (local != 0) {...; local = field }
. 但是,在实际代码中,它们的表达式要复杂得多,例如:while ((((e = (int)(c = ctl)) | (u = (int)(c >>> 32))) & (INT_SIGN|SHORT_SIGN)) == (INT_SIGN|SHORT_SIGN) && e >= 0) {
. 将其重写为一系列临时变量赋值和条件语句会将其从两行代码更改为半屏代码,并且拥有此类非平凡代码代码的两个副本(循环之前和循环体内部)将具有可维护性和可读性恶梦。
整个函数中的代码大小和临时局部变量的数量也可能会增加,这可能会影响性能或至少会使优化器的工作更加困难。内联版本可以编译为:label loop_start; calculate condition; if (!condition) goto after_loop; loop_body; goto loop_start; label after_loop;
虽然我怀疑编译器总是足够聪明,可以自行对循环条件显式计算两次的代码进行重复数据删除。