Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is static final slower than a new on each iteration

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)

like image 868
qwertzguy Avatar asked Jun 23 '16 18:06

qwertzguy


1 Answers

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.

like image 100
apangin Avatar answered Nov 15 '22 21:11

apangin