Java 为什么每次迭代静态final都比新的慢

Java 为什么每次迭代静态final都比新的慢,java,performance,static,Java,Performance,Static,为什么代码段A比代码段B慢14倍? (在Windows 7 64位上使用jdk1.8.0_60进行测试) 代码段A: 导入java.awt.geom.RoundRectangle2D; 公开课考试{ 私有静态最终RoundRectangle2D.Double矩形=新的RoundRectangle2D.Double(1,2,3,4,5,6); 公共静态void main(字符串[]args){ int result=RECTANGLE.hashCode(); 长启动=System.nanoTime

为什么代码段A比代码段B慢14倍?
(在Windows 7 64位上使用jdk1.8.0_60进行测试)

代码段A:

导入java.awt.geom.RoundRectangle2D;
公开课考试{
私有静态最终RoundRectangle2D.Double矩形=新的RoundRectangle2D.Double(1,2,3,4,5,6);
公共静态void main(字符串[]args){
int result=RECTANGLE.hashCode();
长启动=System.nanoTime();
对于(int i=0;i<100_000;i++){
result+=RECTANGLE.hashCode();//在第一种情况下(静态final),JVM需要从内存中读取对象字段。
在第二种情况下,已知值是常量。此外,由于对象没有从循环中逃逸,因此消除了分配,例如,它的字段被局部变量替换

以下基准支持该理论:

package bench;

import org.openjdk.jmh.annotations.*;
import java.awt.geom.RoundRectangle2D;

@State(Scope.Benchmark)
public class StaticRect {
    private static final RoundRectangle2D.Double RECTANGLE =
            new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6);

    @Benchmark
    public long baseline() {
        return 0;
    }

    @Benchmark
    public long testNew() {
        return new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6).hashCode();
    }

    @Benchmark
    @Fork(jvmArgs = "-XX:-EliminateAllocations")
    public long testNewNoEliminate() {
        return new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6).hashCode();
    }

    @Benchmark
    public int testStatic() {
        return RECTANGLE.hashCode();
    }
}
结果:

Benchmark                      Mode  Cnt   Score   Error  Units
StaticRect.baseline            avgt   10   2,840 ± 0,048  ns/op
StaticRect.testNew             avgt   10   2,831 ± 0,011  ns/op
StaticRect.testNewNoEliminate  avgt   10   8,566 ± 0,036  ns/op
StaticRect.testStatic          avgt   10  12,689 ± 0,057  ns/op
testNew
与返回常量一样快,因为对象分配被消除,并且在JIT编译期间,
hashCode
是常量折叠的

当禁用
EliminateAllocations
优化时,基准时间明显较高,但
hashCode
的算术计算仍然是常数折叠的


在上一个基准测试中,即使
RECTANGLE
声明为final,其字段在理论上可能会更改,因此JIT无法消除字段访问。

您的测试没有考虑JVM预热时间/启动时间。您的结果可能与编写的结果不一致。您实际上是在测试JVM启动的速度,然后执行您的代码。@SnakeDoc,JVM预热/启动当然是一个考虑因素,但它并不能解释我看到的OP代码的性能差异。即使在将预热循环插入较慢的代码中之后,也会(改进)性能并没有接近更快的性能。不,我尝试在一个应用程序中运行案例A,然后运行案例B,然后颠倒顺序,在这两种情况下,案例A都是21xlonger@qwertzguy如果它真的完全相同,那么就没有区别了。字节码中有些不同,也许你只是缺少了它leThreshold=0,结果正如我们所期望的,A比B快。它的JIT优化魔法。通过常数折叠,您的意思是将各种
RoundRectangle2D.Double
字段提取到局部变量中,然后对这些变量计算
hashCode
,最终发现它总是返回相同的值?大概是y您可以通过将其中一个构造函数参数更改为计算变量来打破这一切(在本例中,计算结果为2)?所以在上一个基准测试中,它无法优化的唯一原因是对矩形字段的更改可以由另一个线程完成?@SotiriosDelimanolis是的,JIT意识到表达式的所有操作数都是常量,因此表达式也是常量。如果添加变量,表达式将在运行时计算,但这将不一定需要内存加载。@qwertzguy矩形对象不在本地上下文中;HotSpot通常不会消除可能从本地作用域逃逸的对象的字段访问。虽然这不是绝对不可能的,但这样的优化相当困难,通常不值得。总之,这意味着将对象设置为静态final而不是每次创建一个新实例可能会比较慢(当构造函数很快时),这使得很难有一个定义良好的最佳实践。
package bench;

import org.openjdk.jmh.annotations.*;
import java.awt.geom.RoundRectangle2D;

@State(Scope.Benchmark)
public class StaticRect {
    private static final RoundRectangle2D.Double RECTANGLE =
            new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6);

    @Benchmark
    public long baseline() {
        return 0;
    }

    @Benchmark
    public long testNew() {
        return new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6).hashCode();
    }

    @Benchmark
    @Fork(jvmArgs = "-XX:-EliminateAllocations")
    public long testNewNoEliminate() {
        return new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6).hashCode();
    }

    @Benchmark
    public int testStatic() {
        return RECTANGLE.hashCode();
    }
}
Benchmark                      Mode  Cnt   Score   Error  Units
StaticRect.baseline            avgt   10   2,840 ± 0,048  ns/op
StaticRect.testNew             avgt   10   2,831 ± 0,011  ns/op
StaticRect.testNewNoEliminate  avgt   10   8,566 ± 0,036  ns/op
StaticRect.testStatic          avgt   10  12,689 ± 0,057  ns/op