Java – why is static final slower than new at each iteration

Why is code snippet 14 times slower than code snippet B?

Code snippet A:

import java.awt.geom.RoundRectangle2D;

public class Test {
    private static final RoundRectangle2D.Double RECTANGLE = new RoundRectangle2D.Double(1,2,3,4,5,6);

    public static void main(String[] args) {
        int result = RECTANGLE.hashCode();
        long start = System.nanoTime();
        for (int i = 0; i < 100_000_000; i++) {
            result += RECTANGLE.hashCode();            // <= Only change is on this line
        }
        System.out.println((System.nanoTime() - start) / 1_000_000);
        System.out.println(result);
    }
}

Code snippet B:

import java.awt.geom.RoundRectangle2D;

public class Test {
    private static final RoundRectangle2D.Double RECTANGLE = new RoundRectangle2D.Double(1,6);

    public static void main(String[] args) {
        int result = RECTANGLE.hashCode();
        long start = System.nanoTime();
        for (int i = 0; i < 100_000_000; i++) {
            result += new RoundRectangle2D.Double(1,6).hashCode();
        }
        System.out.println((System.nanoTime() - start) / 1_000_000);
        System.out.println(result);
    }
}

TL; Dr: using the new keyword in a loop is faster than accessing a static final field

(Note: deleting the final keyword on rectange will not change the execution time)

Solution

In the first case (static final), the JVM needs to read the object field from memory

The following jmh benchmarks support the theory:

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,6);

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

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

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

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

result:

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 is as fast as returning a constant because the object allocation is eliminated and the hashcode remains unchanged during JIT compilation

When elimination allocation optimization is disabled, the reference time is significantly higher, but the arithmetic calculation of hashcode remains unchanged

In the last benchmark, even if rectange is declared final, its fields may be changed theoretically, so JIT cannot eliminate field access

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>