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