JDK-8200412 : Performance issue with scalar replacement for object with final fields
  • Type: Bug
  • Component: hotspot
  • Sub-Component: compiler
  • Affected Version: 8
  • Priority: P3
  • Status: Closed
  • Resolution: Won't Fix
  • OS: generic
  • CPU: x86_64
  • Submitted: 2018-03-22
  • Updated: 2018-08-14
  • Resolved: 2018-08-14
Description
FULL PRODUCT VERSION :
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)

FULL OS VERSION :
Windows 7 x64 (Microsoft Windows [Version 6.1.7601])


EXTRA RELEVANT SYSTEM CONFIGURATION :
CPU: Intel Core i7-4710MQ @ 2.50GHz,
Laptop: Lenovo ThinkPad T440p.

A DESCRIPTION OF THE PROBLEM :
The scalar replacement optimization produces for two almost identical classes different code with different performance (up to 3 times). One class has final fields, but the second one doesn't.
It seems to be a bug in JRE 1.8.0, because the same benchmark works with no performance difference on JRE 9.0.4.

THE PROBLEM WAS REPRODUCIBLE WITH -Xint FLAG: No

THE PROBLEM WAS REPRODUCIBLE WITH -server FLAG: Yes

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Touch this gist https://gist.github.com/gnkoshelev/97bdff9a645197a3903a3e43eaab72a7 - simple benchmark demonstrates strange performance results.
I've run JMH benchmarks without any specific options: java -jar benchmarks.jar

EXPECTED VERSUS ACTUAL BEHAVIOR :
Both benchmarks should provide the same results on JRE 1.8.0 as on JRE 9.0.4.
REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package ru.gnkoshelev.performance.tests;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OperationsPerInvocation;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

import java.util.concurrent.TimeUnit;

/**
 * Created by kgn on 20.03.2018.
 */
@Fork(value = 1, warmups = 0)
@Warmup(iterations = 5, time = 1_000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 1_000, timeUnit = TimeUnit.MILLISECONDS)
@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Benchmark)
public class FinalOrNotFinalBenchmark {
    private double x1, y1, z1;
    private double x2, y2, z2;

    @Setup(value = Level.Iteration)
    public void setup() {
        x1 = 123.4;
        y1 = 234.5;
        z1 = 345.6;
        x2 = 456.7;
        y2 = 567.8;
        z2 = 678.9;
    }

    @Benchmark
    @OperationsPerInvocation(10_000)
    public void computeWithFinalsBenchmark(Blackhole bh) {
        double sum = 0;
        for (int i = 0; i < 10_000; i++) {
            sum += computeWithFinals(x1, y1, z1, x2, y2, z2);
        }
        bh.consume(sum);
    }

    @Benchmark
    @OperationsPerInvocation(10_000)
    public void computeWithNonFinalsBenchmark(Blackhole bh) {
        double sum = 0;
        for (int i = 0; i < 10_000; i++) {
            sum += computeWithNonFinals(x1, y1, z1, x2, y2, z2);
        }
        bh.consume(sum);
    }

    public static double computeWithFinals(
            double x1, double y1, double z1,
            double x2, double y2, double z2) {
        FinalVector v1 = new FinalVector(x1, y1, z1);
        FinalVector v2 = new FinalVector(x2, y2, z2);
        return v1.crossProduct(v2).squared();
    }

    public static double computeWithNonFinals(
            double x1, double y1, double z1,
            double x2, double y2, double z2) {
        NonFinalVector v1 = new NonFinalVector(x1, y1, z1);
        NonFinalVector v2 = new NonFinalVector(x2, y2, z2);
        return v1.crossProduct(v2).squared();
    }

    public final static class FinalVector {
        private final double x, y, z;

        public FinalVector(double x, double y, double z) {
            this.x = x; this.y = y; this.z = z;
        }

        public double squared() {
            return x * x + y * y + z * z;
        }

        public FinalVector crossProduct(FinalVector v) {
            return new FinalVector(
                    y * v.z - z * v.y,
                    z * v.x - x * v.z,
                    x * v.y - y * v.x);
        }
    }

    public final static class NonFinalVector {
        private double x, y, z;

        public NonFinalVector(double x, double y, double z) {
            this.x = x; this.y = y; this.z = z;
        }

        public double squared() {
            return x * x + y * y + z * z;
        }

        public NonFinalVector crossProduct(NonFinalVector v) {
            return new NonFinalVector(
                    y * v.z - z * v.y,
                    z * v.x - x * v.z,
                    x * v.y - y * v.x);
        }
    }
}
---------- END SOURCE ----------


Comments
Original issue JDK-8139758 is performance enhancement. We will not be fixing this in 8. Closing as wnf
14-08-2018

After bit more analysis found that issue fixed in 9 ea b96 onwards (suspect - JDK-8139758)
29-03-2018

Received benchmarks.jar file from submitter and could able to reproduce this issue. This issue is reproducible only in 8, it works fine 9, 10 and 11 8uxx (Verified 8GA, 8u161, 8u172 ea b03) - Fail 9.0.4 - Pass 10 GA - Pass 11 ea b03 - Pass Comparison with JDK8 along with JDK10 JDK8 (WithFinals = 3xWithNonFinals) Benchmark Mode Cnt Score Error Units FinalOrNotFinalBenchmark.computeWithFinalsBenchmark avgt 30 3.305 �� 0.088 ns/op FinalOrNotFinalBenchmark.computeWithNonFinalsBenchmark avgt 30 1.133 �� 0.024 ns/op JDK10 (WithFinals ~= WithNonFinals) Benchmark Mode Cnt Score Error Units FinalOrNotFinalBenchmark.computeWithFinalsBenchmark avgt 30 1.110 �� 0.014 ns/op FinalOrNotFinalBenchmark.computeWithNonFinalsBenchmark avgt 30 1.121 �� 0.018 ns/op
29-03-2018

There are few dependencies here "benchmarks.jar" is being referred, not clear about the version. Provided test case has dependencies with jmh-core-1.3.2.jar and jmh-generator-annprocess-1.3.2 jar files. Downloaded all the dependent files and compilation is succeded, but there are no instructions to execute the provided test case. Requested submitter for steps to reproduce this issue.
23-03-2018