测试 Lambda 表达式性能的正确方法?

Right way to test performance of Lambda expressions?

我已经使用 JMH 来测试 Lambda 对 Anonymous Inner 类 的性能,下面是我是如何做到的:

public class LambdaVsAnonymousClass {

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void testLambda() {
    // nonCapturing expressions
    NonCapturing nonCapturing = () -> System.out.println("ram");
    nonCapturing.test();
    // nonCapturing expressions
    NonCapturing nonCapturing2 = () -> System.out.println("bon");
    nonCapturing2.test();
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void testAnonymousClass() {
    // anonymous classes
    NonCapturing nonCapturing = new NonCapturing() {
        @Override
        public void test() {
            System.out.println("ram");
        }
    };
    nonCapturing.test();
    // anonymous classes
    NonCapturing nonCapturing2 = new NonCapturing() {
        @Override
        public void test() {
            System.out.println("bon");
        }
    };
    nonCapturing2.test();
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void testLambdaCapturing() {
    // lambda expressions
    final int i = 1;
    Capturing capturing = n -> System.out.println(n);
    capturing.test(i);
    // lambda expressions
    final int j = 2;
    Capturing capturing2 = n -> System.out.println(n);
    capturing2.test(j);
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void testAnonymousClassCapturing() {
    // anonymous classes
    final int i = 1;
    Capturing capturing = new Capturing() {
        @Override
        public void test(int n) {
            System.out.println(n);
        }
    };
    capturing.test(i);
    // anonymous classes
    final int j = 2;
    Capturing capturing2 = new Capturing() {
        @Override
        public void test(int n) {
            System.out.println(n);
        }
    };
    capturing2.test(j);
}

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(LambdaVsAnonymousClass.class.getSimpleName())
            .warmupIterations(5)
            .measurementIterations(5)
            .forks(1)
            .build();

    new Runner(opt).run();
}
}

@FunctionalInterface
interface NonCapturing {
    void test();
}

@FunctionalInterface
interface Capturing {
    void test(int n);
}

我原以为 Lambda 会更快完成,但我在下面得到了相反的结果。我是否错误地测试了它?如果是,实际测试 lambda 表达式的更快性能的正确方法是什么。

# Run complete. Total time: 00:00:42

Benchmark                                           Mode  Cnt   Score    Error  Units
LambdaVsAnonymousClass.testAnonymousClass           avgt    5  16.592 ±  4.084  us/op
LambdaVsAnonymousClass.testAnonymousClassCapturing  avgt    5  18.916 ±  4.469  us/op
LambdaVsAnonymousClass.testLambda                   avgt    5  18.618 ±  4.060  us/op
LambdaVsAnonymousClass.testLambdaCapturing          avgt    5  22.008 ± 16.729  us/op

NOTE: The concept of capturing lambda expressions is incorrectly shown in the code. Read comments from @Holger for more understanding.

不是很清楚你到底想测试什么。创建然后调用?如果是这样,然后删除 System.out 因为它们咬你很厉害然后输出应该在 nano-seconds.

中测量

这种情况下的结果(毫不奇怪)大致相同:

LambdaVsClass.testAnonymousClass           avgt    5  0.335 ± 0.069  ns/op
LambdaVsClass.testAnonymousClassCapturing  avgt    5  0.337 ± 0.051  ns/op
LambdaVsClass.testLambda                   avgt    5  0.331 ± 0.051  ns/op 
LambdaVsClass.testLambdaCapturing          avgt    5  0.337 ± 0.043  ns/op  

如果您在 invokedynamic(没有任何预热)引导 lambda 时测量 SingleShotTime,结果应该不同。

您应该对基准测试结果持批判态度,并理解您所看到的。 ±4 的错误大于 16.618.6.
的“非捕获”测试执行时间之间的差异 当您看到 22 错误 ±16.7!

这样的结果时,它肯定会敲响警钟

另外,你对“捕获”的理解有误。在您的测试中,没有捕获 lambda 表达式或匿名 class。您只有一个参数为零的函数和一个参数为一个的函数。这与捕获无关。唯一的区别是,在一种情况下,您正在打印现有的 String,而在另一种情况下,您正在执行 intString 的转换。但即使是这些差异也小于报告的错误。

当考虑错误时,结果在相同的数量级内,当然,打印代码的性能不取决于它是驻留在 lambda 表达式还是匿名内部 class。目前尚不清楚为什么您希望 lambda 表达式更快。 Lambda 表达式并不神奇。