Подтвердить что ты не робот

Большая разница в времени выполнения между java Lambda vs Анонимный класс

Мне было интересно узнать о производительности создания экземпляров java8 lambda против одного и того же анонимного класса. (Измерение выполняется на win32 java build 1.8.0-ea-b106). Я создал очень простой пример и измерил, если java предложит некоторую оптимизацию оператора new при создании выражения лямбда:

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);
    }
}

(Полный код можно сделать там: http://codepad.org/Iw0mkXhD). Результат довольно предсказуем - лямбда выигрывает 2 раза.

Но на самом деле малое изменение сделать closure показывает очень плохое время для лямбда. Анонимный класс выигрывает 10 раз! Итак, теперь анонимный класс выглядит следующим образом:

ICallback clb = new ICallback() {
        @Override
        public void payload() {
            arr[0]++;
        }
    };

И лямбда делает следующее:

ICallback clb = () -> {
            arr[0]++;
        };

(Полный код можно сделать там: http://codepad.org/XYd9Umty) Может ли кто-нибудь объяснить мне, почему существует такая большая (плохая) разница в обращении с закрытием?

4b9b3361

Ответ 1

UPDATE

Несколько комментариев, интересующихся, был ли мой бенчмарк внизу испорчен - после введения большого количества случайности (чтобы JIT не мог оптимизировать слишком много вещей), я все равно получаю похожие результаты, поэтому я склонен думать, что все в порядке.

Тем временем я встретил эту презентацию командой лямбда-реализации. На рисунках 16 показаны некоторые показатели производительности: внутренние классы и блокировки имеют аналогичную производительность/неконвертируемость лямбда в 5 раз быстрее.

И @StuartMarks разместили эту очень интересную ссылку, которая анализирует производительность лямбда. Суть в том, что компиляция пост JIT, lambdas и анонимные классы аналогичным образом выполняются в текущих реализациях JVM Hostpot.


ВАШ ЭТАЛОН

Я также проверил ваш тест, когда вы его разместили. Проблема в том, что он работает всего за 20 мс для первого метода и 2 мс для второго. Хотя это соотношение 10: 1, оно никоим образом не является репрезентативным, потому что время измерения слишком мало.

Затем я изменил ваш тест, чтобы разрешить больше прогревания JIT, и получаю аналогичные результаты, как с jmh (т.е. никакой разницы между анонимным классом и лямбдой).

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);
    }
}

Последний запуск занимает около 28 секунд для обоих методов.


JMH MICRO BENCHMARK

Я пропустил тот же тест с jmh, и в нижней строке указано, что четыре метода занимают столько же времени, сколько и эквивалент:

void baseline() {
    arr[0]++;
}

Другими словами, JIT включает как анонимный класс, так и лямбда, и они принимают ровно одно и то же время.

Итоговый результат:

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