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

Java 测试Lambda表达式性能的正确方法?,java,lambda,java-8,anonymous-inner-class,Java,Lambda,Java 8,Anonymous Inner Class,我曾经针对匿名内部类测试Lambda的性能,下面是我是如何做到这一点的: public class LambdaVsAnonymousClass { @Benchmark @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) public void testLambda() { // nonCapturing expressions NonCapturing nonCapturing =

我曾经针对匿名内部类测试Lambda的性能,下面是我是如何做到这一点的:

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的评论,了解更多信息

你到底想测试什么还不是很清楚。创造然后调用?如果是这样的话,那么就把系统移除,因为它们咬得很厉害,输出应该以纳秒为单位

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

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  

如果在InvokedDynamic(无任何预热)引导lambda时测量SingleShotTime,则结果应该有所不同。

您应该对基准测试结果持批评态度,并了解您所看到的内容。
±4
的错误大于您的“非捕获”测试执行时间
16.6
18.6
之间的差异
当你看到像
22
这样的错误为
±16.7
的结果时,它肯定会敲响警钟

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


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

我会尝试在基准测试期间不打印到控制台(
System.out.println()
),因为控制台/操作系统会对性能产生巨大影响,因此可能会扭曲结果。请看一看。除非您是足够的性能专家,比如说,编写JMH,我的建议是:不要再担心nano性能问题,而是专注于编写清晰的代码。他们足够快了。@BrianGoetz我实际上是在为我大学的演讲做准备。但是非常感谢你的建议。我认为Lambdas速度更快,因为不需要创建单独的类文件和加载类等,因为Lambdas被转换为静态方法,然后还有lambda工厂的优化,我猜这是一条冷路。请纠正我。我不理解你的话,“没有捕捉lambda表达式”。我想我正在将
final
变量传递给lambda,它位于lambda表达式之外。你能纠正我吗?你正在把一个值作为参数传递给接口方法。这个值也可以是一个常量,比如
42
,或者是一个表达式,比如
“bla”.length()
,或者是一个可变变量,这无关紧要。这与捕获无关。捕获发生在lambda表达式本身从外部上下文引用变量时,而不是在使用参数时。与加载类相比,lambda表达式的冷启动速度更快,但这仅适用于第一次执行,因为类只加载一次。在测量另外五次执行之前,您正在进行五次预热迭代。你希望如何见证第一次执行的不同之处?此外,当您对只包含两个lambda表达式的方法进行基准测试时,冷启动性能将因lambda元工厂的初始化而黯然失色,因为该类(以及内部使用的所有类)也必须加载。您的评论澄清了@holger的许多问题