尝试对 lambda 性能进行基准测试

Trying to benchmark lambda performance

我读过这个post:Performance difference between Java 8 lambdas and anonymous inner classes and provided there article

上面写着:

Lambda invocation behaves exactly as anonymous class invocation

"Ok"我说了决定写自己的benchmark,我用过jmh,下面是(我也加了benchmark作为方法参考)

public class MyBenchmark {

    public static final int TESTS_COUNT = 100_000_000;

    @Benchmark
    public void testMethod_lambda() {
        X x = i -> test(i);
        for (long i = 0; i < TESTS_COUNT; i++) {
            x.x(i);
        }
    }
    @Benchmark
    public void testMethod_methodRefernce() {
        X x = this::test;
        for (long i = 0; i < TESTS_COUNT; i++) {
            x.x(i);
        }
    }
    @Benchmark
    public void testMethod_anonymous() {
        X x = new X() {
            @Override
            public void x(Long i) {
                test(i);
            }
        };
        for (long i = 0; i < TESTS_COUNT; i++) {
            x.x(i);
        }
    }

    interface X {
        void x(Long i);
    }

    public void test(Long i) {
        if (i == null) System.out.println("never");
    }
}

结果(在 Intel Core i7 4770k 上)是:

Benchmark                                     Mode  Samples   Score  Score error  Units
t.j.MyBenchmark.testMethod_anonymous         thrpt      200  16,160        0,044  ops/s
t.j.MyBenchmark.testMethod_lambda            thrpt      200   4,102        0,029  ops/s
t.j.MyBenchmark.testMethod_methodRefernce    thrpt      200   4,149        0,022  ops/s

因此,如您所见,lambda 和匿名方法调用之间存在 4 倍的差异,其中 lambda 慢 4 倍。

问题是:我哪里做错了或者我对lambdas的性能理论有误解?

编辑:

# VM invoker: C:\Program Files\Java\jre1.8.0_31\bin\java.exe
# VM options: <none>
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each

问题出在你的基准测试中:你是死代码消除的受害者。

JIT-compiler 非常聪明地理解有时自动装箱的结果永远不会为空,因此对于匿名 class 它只是删除了您的检查,这反过来使循环体几乎为空。将其替换为不太明显的内容(对于 JIT),如下所示:

public void test(Long i) {
    if (i == Long.MAX_VALUE) System.out.println("never");
}

你会观察到相同的性能(匿名 class 变得更慢,而 lambda 和方法引用在同一水平上执行)。

对于lambda/method参考,出于某种原因它没有进行相同的优化。但是您不必担心:您不太可能在实际代码中拥有这种可以完全优化掉的方法。

总的来说@apangin 是对的:改用 Blackhole。

除了@TagirValeev 提出的问题之外,您采用的基准测试方法存在根本性缺陷,因为您测量的是综合指标(尽管您尝试不这样做。)

您想独立衡量的重要成本是链接捕获调用 .但是你所有的测试都会把每个测试的一部分混在一起,毒害你的结果。我的建议是只关注调用成本——这与整个应用程序吞吐量最相关,也是最容易衡量的(因为它受多级缓存的影响较小。)

底线:在动态编译环境中测量性能确实非常困难。即使有 JMH。

我的问题是您不应该如何进行基准测试的另一个例子。我已经根据此处其他答案中的建议重新创建了我的测试。

希望现在接近正确,因为它表明lambda 和anon 的方法调用性能之间没有任何显着差异。请看下面:

@State(Scope.Benchmark)
public class MyBenchmark {

    @Param({"1", "100000", "500000"})
    public int arg;

    @Benchmark
    public void testMethod_lambda(Blackhole bh) {
        X x = (i, bh2) -> test(i, bh2);
        x.x(arg, bh);
    }

    @Benchmark
    public void testMethod_methodRefernce(Blackhole bh) {
        X x = this::test;
        x.x(arg, bh);
    }

    @Benchmark
    public void testMethod_anonymous(Blackhole bh) {
        X x = new X() {
            @Override
            public void x(Integer i, Blackhole bh) {
                test(i, bh);
            }
        };
        x.x(arg, bh);
    }

    interface X {
        void x(Integer i, Blackhole bh);
    }

    public void test(Integer i, Blackhole bh) {
        bh.consume(i);
    }
}
Benchmark                                     (arg)   Mode  Samples          Score  Score error  Units
t.j.MyBenchmark.testMethod_anonymous              1  thrpt      200  415893575,928  1353627,574  ops/s
t.j.MyBenchmark.testMethod_anonymous         100000  thrpt      200  394989882,972  1429490,555  ops/s
t.j.MyBenchmark.testMethod_anonymous         500000  thrpt      200  395707755,557  1325623,340  ops/s
t.j.MyBenchmark.testMethod_lambda                 1  thrpt      200  418597958,944  1098137,844  ops/s
t.j.MyBenchmark.testMethod_lambda            100000  thrpt      200  394672254,859  1593253,378  ops/s
t.j.MyBenchmark.testMethod_lambda            500000  thrpt      200  394407399,819  1373366,572  ops/s
t.j.MyBenchmark.testMethod_methodRefernce         1  thrpt      200  417249323,668  1140804,969  ops/s
t.j.MyBenchmark.testMethod_methodRefernce    100000  thrpt      200  396783159,253  1458935,363  ops/s
t.j.MyBenchmark.testMethod_methodRefernce    500000  thrpt      200  395098696,491  1682126,737  ops/s