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

Java 尝试对lambda性能进行基准测试,java,performance,lambda,java-8,microbenchmark,Java,Performance,Lambda,Java 8,Microbenchmark,我读过这篇文章:并在那里提供 上面写着: Lambda调用的行为与匿名类调用完全相同 “好的”我说了,并决定编写自己的基准测试,我使用了jmh,下面是它(我还添加了基准测试以供方法参考) 所以,正如您所看到的,lambda和匿名方法调用之间有4倍的差异,其中lambda慢4倍 问题是:我做错了什么,或者我对兰博达斯的绩效理论有误解 编辑: # VM invoker: C:\Program Files\Java\jre1.8.0_31\bin\java.exe # VM options: <

我读过这篇文章:并在那里提供

上面写着:

Lambda调用的行为与匿名类调用完全相同

“好的”我说了,并决定编写自己的基准测试,我使用了
jmh
,下面是它(我还添加了基准测试以供方法参考)

所以,正如您所看到的,lambda和匿名方法调用之间有4倍的差异,其中lambda慢4倍

问题是:我做错了什么,或者我对兰博达斯的绩效理论有误解

编辑

# 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
#VM调用程序:C:\Program Files\Java\jre1.8.0\u 31\bin\Java.exe
#虚拟机选项:
#预热:20次迭代,每次1s
#测量:20次迭代,每次1s

问题在于您的基准测试:您是死代码消除的受害者

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

public void test(Long i) {
    if (i == Long.MAX_VALUE) System.out.println("never");
}
您将观察到相同的性能(匿名类变慢,而lambda和方法引用的性能相同)

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


一般来说,@apangin是正确的:使用黑洞来代替。

除了@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

查看生成的字节码,我注意到匿名类使用
invokedespecial
来构造对象,而lambda使用
invokedynamic
。我很好奇,如果在测试本身之外构造每个对象的一个实例,是否会有速度差异?JMH为你做循环。你为什么用“从不”这个东西来代替黑洞?编译器足够聪明,可以对其进行优化。此外,您引用的“Lambda调用的行为与匿名类调用完全相同”。。您很方便地省略了脚注。请注意,如果您将lambda、方法引用和匿名类的创建删除到私有静态字段(并重用它们),您的基准测试显示方法引用和匿名类具有相同的性能,而lambda的性能较差。“我做错了什么?”-1)手动编写基准循环是错误的;JMH为你做的。2) Blackhole=>JVM可能会对代码进行奇怪的优化,结果不会被Blackhole=>JVM所消耗,而且您将无法衡量预期的结果。是的,我刚刚测试过,这确实是问题所在。一般来说@Andremoniy,您不应该尝试在基准测试中变得聪明,JIT或者编译器会变得更聪明。@TagirValeev yep,看起来我真的弄错了。非常感谢您的介绍,我真的很惊讶
i==Long.MAX\u VALUE
是“不太明显”的东西,足以愚弄JVM。毕竟,我们有一个完全可预测的循环计数器和一个装箱,JVM知道这是一个操作……我认为您仍然希望将IC/lambda的创建分解为静态代码。否则,您仍然会用链接/捕获成本毒害某些调用度量。@BrianGoetz好吧,我只想表明我之前的结论是错误的,正确的基准测试表明调用性能没有任何差异。在这种情况下,我错了什么?你在测量两个不同操作的成本总和,这两个操作具有独立的性能配置文件。比如衡量“去商店买牛奶”和“坐在餐桌旁喝一杯牛奶”的成本。因为,你的日常操作不是“买牛奶+喝牛奶”,而是“喝牛奶”。碰巧你必须先买一些才能喝。但很明显,牛奶购买受到很多因素的影响(一天中的时间、交通模式、商店营业时间等),而这些因素与饮用牛奶无关。你想分别测量每一个,我想你还是不明白。链接(a)比调用要昂贵得多,(b)只做一次,以及(c)受到许多因素的扭曲。如果您认为常见的情况是链接+调用一次,那么根据定义,您几乎不关心性能!(内部类的链接涉及到文件系统、读取磁盘上的类文件字节以及解析/验证/加载类。)这就像说常见的操作是“建造大楼,然后乘电梯到10楼。”让性能分析变得困难的部分原因是知道测量什么!您仍然可以使用
test
作为实例方法;静态捕获X的一个实例,然后在@Bench方法中测量X.X(..)。
public void test(Long i) {
    if (i == Long.MAX_VALUE) System.out.println("never");
}
@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