尝试对 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
我读过这个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