java Lambda与Anonymous类之间的执行时间差异很大

Dew*_*wfy 18 java performance lambda closures java-8

我很好奇在同一个匿名类中创建java8 lambda实例的性能.(在win32 java build 1.8.0-ea-b106上执行测量).我创建了一个非常简单的示例,并测量了java是否new在创建lambda表达式时提出了一些运算符优化:

static final int MEASURES = 1000000;
static interface ICallback{
    void payload(int[] a);
}
/**
* force creation of anonymous class many times
*/
static void measureAnonymousClass(){
    final int arr[] = {0};
    for(int i = 0; i < MEASURES; ++i){
        ICallback clb = new ICallback() {
            @Override
            public void payload(int[] a) {
                a[0]++;
            }
        };
        clb.payload(arr);
    }
}
/**
* force creation of lambda many times 
*/
static void measureLambda(){ 
    final int arr[] = {0};
    for(int i = 0; i < MEASURES; ++i){
        ICallback clb = (a2) -> {
            a2[0]++;
        };
        clb.payload(arr);
    }
}
Run Code Online (Sandbox Code Playgroud)

(完整的代码可以在那里:http://codepad.org/Iw0mkXhD)结果是可以预测的 - lambda赢了2次.

但实际上很少转换到关闭显示lambda非常糟糕的时间.匿名课程赢了10次!所以现在匿名类看起来像:

ICallback clb = new ICallback() {
        @Override
        public void payload() {
            arr[0]++;
        }
    };
Run Code Online (Sandbox Code Playgroud)

而lambda的确如下:

ICallback clb = () -> {
            arr[0]++;
        };
Run Code Online (Sandbox Code Playgroud)

(可以在那里获取完整的代码:http://codepad.org/XYd9Umty)任何人都可以解释为什么在处理闭包时存在如此大(差)的差异?

ass*_*ias 27

UPDATE

一些评论想知道我的底部基准是否存在缺陷 - 在引入大量随机性(以防止JIT优化太多东西)后,我仍然得到类似的结果,所以我倾向于认为它没问题.

与此同时,我发现了lambda实施团队的这个演示.第16页显示了一些性能数据:内部类和闭包具有相似的性能/非捕获lambda的速度提高了5倍.

@StuartMarks发布了这个非常有趣的链接,它解析了lambda性能.底线是JIT后编译,lambdas和匿名类在当前的Hostpot JVM实现上执行类似的操作.


你的基准

当你发布它时,我也会运行你的测试.问题是第一种方法运行时间仅为20 ms,第二种方法运行时间仅为2 ms.虽然这是10:1的比例,但它绝不具有代表性,因为测量时间太小.

然后我修改了你的测试以允许更多的JIT热身,我得到与jmh类似的结果(即匿名类和lambda之间没有区别).

public class Main {

    static interface ICallback {
        void payload();
    }
    static void measureAnonymousClass() {
        final int arr[] = {0};
        ICallback clb = new ICallback() {
            @Override
            public void payload() {
                arr[0]++;
            }
        };
        clb.payload();
    }
    static void measureLambda() {
        final int arr[] = {0};
        ICallback clb = () -> {
            arr[0]++;
        };
        clb.payload();
    }
    static void runTimed(String message, Runnable act) {
        long start = System.nanoTime();
        for (int i = 0; i < 10_000_000; i++) {
            act.run();
        }
        long end = System.nanoTime();
        System.out.println(message + ":" + (end - start));
    }
    public static void main(String[] args) {
        runTimed("as lambdas", Main::measureLambda);
        runTimed("anonymous class", Main::measureAnonymousClass);
        runTimed("as lambdas", Main::measureLambda);
        runTimed("anonymous class", Main::measureAnonymousClass);
        runTimed("as lambdas", Main::measureLambda);
        runTimed("anonymous class", Main::measureAnonymousClass);
        runTimed("as lambdas", Main::measureLambda);
        runTimed("anonymous class", Main::measureAnonymousClass);
    }
}
Run Code Online (Sandbox Code Playgroud)

两种方法的最后一次运行大约需要28秒.


JMH MICRO BENCHMARK

用jmh运行了相同的测试,底线是这四种方法花费的时间与等价物相同:

void baseline() {
    arr[0]++;
}
Run Code Online (Sandbox Code Playgroud)

换句话说,JIT内联匿名类和lambda,它们完全相同.

结果摘要:

Benchmark                Mean    Mean error    Units
empty_method             1.104        0.043  nsec/op
baseline                 2.105        0.038  nsec/op
anonymousWithArgs        2.107        0.028  nsec/op
anonymousWithoutArgs     2.120        0.044  nsec/op
lambdaWithArgs           2.116        0.027  nsec/op
lambdaWithoutArgs        2.103        0.017  nsec/op
Run Code Online (Sandbox Code Playgroud)

  • 此外,还有两个来自表演人员的JVM语言峰会.首先,Alexey Shipilev(jmh的作者)谈论基准测试及其许多陷阱.(这是今年在JVMLS上被评为最佳演讲.)其次,谢尔盖库库森科谈到了他为优化lambda表现所做的工作.[1] http://medianetwork.oracle.com/video/player/2630310904001 [2] http://medianetwork.oracle.com/video/player/2623576348001 (6认同)
  • 这个结果意味着JIT完全取消了实际的lambda/anonymous类实例的分配.但是,如果OP有不同的结果,那么我将按照描述继续:从调用中分离出分配,看看是否仍存在差异. (2认同)
  • 感谢您写下您的绩效调查.+1.lambda的原始(主要是原型)JDK 8实现完全是一个匿名的内部类,为了让我们能够早期工作,我们可以探索语言和库的演变.这似乎产生了一个神话,即lambdas只不过是匿名的内部类.最近,实现已经过优化,因此lambda几乎总是比"等效"匿名内部类快. (2认同)
  • @Tuntable 链接已更新。感谢您提到这一点。 (2认同)