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

时间:2017-01-13 14:22:33

标签: java lambda java-8 anonymous-inner-class

我使用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
  

注意:捕获lambda表达式的概念在代码中显示不正确。阅读@Holger的评论以获得更多理解。

2 个答案:

答案 0 :(得分:2)

目前还不是很清楚你究竟想要测试什么。创建然后调用?如果是这样,那么删除System.out,因为它们咬你很糟糕,然后输出应该以纳秒为单位进行测量。

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

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  

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

答案 1 :(得分:2)

您应该对基准测试结果至关重要,并了解您所看到的内容。 ±4的错误大于16.618.6的“非捕获”测试执行时间的执行时间之间的差异。
当你看到22的结果,错误为±16.7时,它肯定会响起警钟!

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

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