Why is code snippet A 14x slower than code snippet B?
(tested with jdk1.8.0_60 on Windows 7 64bits)
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, 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 += new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6).hashCode();
}
System.out.println((System.nanoTime() - start) / 1_000_000);
System.out.println(result);
}
}
TL;DR: Using the new
keyword inside a loop is faster than accessing a static final
field.
(note: Removing the final
keyword on RECTANGLE
does not change the execution time)
In the first case (static final) JVM needs to read object fields from memory. In the second case the values are known to be constant. Furthermore, since the object does not escape from the loop, the allocation is eliminated, e.g. its fields are replaced with local variables.
The following JMH benchmark supports 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, 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();
}
}
Results:
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 object allocation is eliminated and the hashCode
is constant-folded during JIT compilation.
When EliminateAllocations
optimization is disabled, the benchmark time is notably higher, but the arithmetic calculations of hashCode
are still constant-folded.
In the last benchmark, even though RECTANGLE
is declared final, its fields could be in theory changed, so JIT cannot eliminate field access.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With