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

Что было бы лучше с точки зрения производительности Lambda или простой петли?

Я быстро прочитал документацию Oracle Lambda Expression.

Этот пример помог мне лучше понять, хотя:

//Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}

//New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));


//or we can use :: double colon operator in Java 8
list.forEach(System.out::println);

Тем не менее, я не понимаю, почему это такое новшество. Это просто метод, который умирает, когда "переменная метода" заканчивается, правильно? Почему я должен использовать это вместо реального метода? Какой будет лучший вариант с точки зрения производительности. Лямбда или простая петля.

4b9b3361

Ответ 1

Мой совет будет:

  1. Используйте стиль, который вы и ваши коллеги соглашаетесь, является наиболее поддерживаемым.

  2. Если вы и ваши коллеги еще не знакомы с лямбдами, продолжайте учиться.

  3. Не зацикливайтесь на производительности. Это часто не самая важная вещь.

Вообще говоря, лямбды и потоки предоставляют более краткий и (как только каждый освоит скорость) более читаемый способ выражения алгоритма такого рода. Производительность не является основной целью.

Если производительность действительно становится проблемой, то стандартный совет - кодировать, тестировать, профилировать, оптимизировать. Вы можете легко тратить время, оптимизируя на этапе кодирования. Позвольте профилировщику указать, какие части вашего кода заслуживают усилий по оптимизации.


В этом конкретном примере разница в производительности будет слишком мала для измерения. И если вы масштабируете список до миллионов элементов, производительность будет зависеть от времени, затраченного на создание списка и написание чисел. Различные способы итерации будут вносить небольшой вклад в общую производительность.


И для людей, которые (несмотря на все вышеперечисленное) все еще хотят знать, быстрее ли использовать лямбду или обычный цикл, лучший общий ответ:

"Это зависит от всевозможных факторов, которые 1) не совсем понятны и 2) подвержены изменениям по мере развития технологии компиляции Java.

Мы могли бы дать вам ответ для конкретного примера с конкретным выпуском майора/минор/патча Java, но было бы неразумно обобщать.

Ответ 2

Почему я должен использовать это вместо реального метода?

Вы не должны. Используйте подход, который вам больше нравится.

Что касается производительности, я думаю, все эти версии примерно одинаково быстры. Здесь операция ввода/вывода (println) намного медленнее всех возможных накладных расходов при вызове лямбда или создании итератора. В общем случае forEach может быть немного быстрее, поскольку он делает все внутри одного метода без создания Iterator и вызова hasNext и next (что неявно выполняется для каждого цикла). В любом случае, это зависит от многих факторов, таких, как часто вы называете этот код, как долго ваш список, компилятор JIT удалось девиртуализировать итератор и/или лямбда и т.д.

Ответ 3

Это позволяет вам писать в одной строке (при наличии достаточной читаемости) что-то, чего раньше не было. Производительность здесь не проблема O(n) остается O(n).

Например, в моем приложении Windows Phone у меня есть сеансы, и в этих сеансах есть исполнители, и я хочу выбрать все сеансы, которые имеют один конкретный исполнитель (например, вы хотите увидеть все фильмы, в которые играет какой-либо актер). В Java 1.7 или меньше мне приходилось создавать цикл с внутренним циклом, проверять его, возвращать null, если нет исполнителей и т.д. И с лямбда-выражениями я могу это сделать:

//performerId is parameter passed by method before
Sessions.Where(x => x.Performers.Where(y => y.PerformerId == performerId).FirstOrDefault() != null)

Это то же самое в Java сейчас (однако я не работаю над проектом 1.8 прямо сейчас, у меня нет Java-примера, но я с нетерпением жду его).

Ответ 4

С точки зрения производительности нормальная функция будет лучше, чем сравнение с лямбда, потому что в groovy присутствуют замыкания, которые больше или похожи на lambda.

Эти вещи работают таким образом, как если бы вы написали замыкание для любой коллекции, он будет внутренне создавать другой класс, который фактически выполняет действие для упомянутого закрытия или выражения лямбда.

Но, используя лямбда и замыкание, я могу лучше итератировать вещи, а также легко отлаживать. Вы можете написать меньше строк кодов.

Ответ 5

Если вы хотите понять значение лямбда-выражений, вы не должны смотреть на единственный новый метод, у которого есть аналогий языка, который существовал раньше. Как насчет этих примеров:

button.addActionListener( ev -> BACKGROUND_EXECUTOR_SERVICE.execute( () -> {
   String result = longComputation();
   SwingUtilities.invokeLater( () -> label.setText(result) );
});

Просто подумайте о том, как выглядит эквивалентный код до Java 8, и вы видите, что главным преимуществом здесь является не производительность.

Если вы хотите посмотреть операции с коллекциями, как насчет этого?

map.merge(key, 1, Integer::sum);

Это поместит 1 в карту, если ключ еще не существует на карте или добавит 1 к значению существующего сопоставления в противном случае. Опять же, подумайте о том, как выглядит старый коллега. Тот факт, что он может быть даже более эффективным, поскольку он избегает нескольких хеш-операций, если карта имеет соответствующую реализацию метода, является лишь дополнительным преимуществом.

Несомненно, метод forEach не может обеспечить такой большой выигрыш в выразительности, поскольку существует для каждого отдельного языка. Тем не менее, не нужно объявлять переменную цикла, может улучшить исходный код, если декларация требует повторить длинное имя типа и общие параметры. Это особенно верно в контексте Map s:

Map<ContainerOrderFocusTraversalPolicy, List<AttributedCharacterIterator>> map;
//…

map.forEach((p,l)->l.forEach(i->{ /* do your work using p and i */}));

здесь эта новая итерация явно выигрывает

for(Map.Entry<ContainerOrderFocusTraversalPolicy, List<AttributedCharacterIterator>> e:
                                                                      map.entrySet()) {
    ContainerOrderFocusTraversalPolicy p=e.getKey();
    for(AttributedCharacterIterator i: e.getValue()) {
        /* do your work using p and i */
    }
}

Конечно, это имеет значение только в том случае, если фактические рабочие операторы достаточно малы, но это то, как следует использовать лямбда-выражения: инкапсулировать небольшие фрагменты кода. И все еще есть задачи, которые не могут быть выполнены таким образом и требуют обычного цикла for-each, так же как и задачи, которые не могут выполняться с помощью цикла for-each, и нуждаются в цикле for с четным старом, работающим с Iterator вручную...

Ответ 6

При использовании лямбда в вашем коде вы говорите, что делать не так, как это сделать. Над кодом, передающим ссылку на метод вместо того, чтобы перебирать список, я думаю, что лямбда-единица более эргономична.

Что касается производительности, то распечатка списка из 1 миллиона элементов заняла почти одно и то же время, но я не проверил других операций.

Приведенный выше код является тривиальной операцией, но лямбда имеет немало преимуществ, поскольку вы можете иметь функции, которые вы можете передавать, используя несколько ячеек (параллельные потоки) и т.д.

Ответ 7

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Timeout;
import org.openjdk.jmh.annotations.Warmup;

/**
 *
 * @author devb
 */
@BenchmarkMode(Mode.Throughput)
@Fork(value = 1)
@Warmup(iterations = 1, time = 32, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 16, time = 1, timeUnit = TimeUnit.SECONDS)
@Timeout(time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class CheckLamdaPerformance {

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

    @Benchmark
    public void testPerOld() {
        //Old way:
        for (Integer n : list) {
            System.out.println(n);
        }
    }

    @Benchmark
    public void testPerNew() {
        //New way:
        list.forEach(n -> System.out.println(n));
    }

    @Benchmark
    public void testPerDoubleColon() {
        //or we can use :: double colon operator in Java 8
        list.forEach(System.out::println);
    }

}

Для тестирования теста

import java.io.IOException;
import org.openjdk.jmh.Main;
import org.openjdk.jmh.runner.RunnerException;

/**
 *
 * @author devb
 */
public class MyBenchmark {

    private static final String TEST = ".*CheckLamdaPerformance.*"; 

    public static void main(String[] args) throws IOException, RunnerException {
        Main.main(getArguments(TEST, 1, 5000, 1));
    }

    private static String[] getArguments(String className, int nRuns, int runForMilliseconds, int nThreads) {
        return new String[]{className,
            "-i", "" + nRuns,
            "-r", runForMilliseconds + "ms",
            "-t", "" + nThreads,
            "-w", "5000ms",
            "-wi", "1"
        };
    }

}

После выполнения выхода:

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

Benchmark                                  Mode  Cnt     Score   Error  Units
CheckLamdaPerformance.testPerDoubleColon  thrpt        776.008          ops/s
CheckLamdaPerformance.testPerNew          thrpt       1096.423          ops/s
CheckLamdaPerformance.testPerOld          thrpt        873.542          ops/s

Ответ 8

Посмотрите первую часть в Документация Guava. Речь идет о более старой версии Java, но делает важный момент - использование lambdas во всем мире может сделать код менее читаемым. Лучшим примером может быть тот, что из Stream API:

 // Group employees by department
 Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));

Ответ 9

Время выполнения и читаемость, безусловно, меняются в каждом конкретном случае, но, на мой взгляд, есть неплохой ответ на

Почему я должен использовать это вместо реального метода?

Вы можете передать лямбда-функцию как переменную
Исправьте меня, если я не согласен с этим, так как я уже давно являюсь пользователем Fantom, но вы можете передать функцию Lambda в качестве аргумента вызова метода. Например, если у вас есть метод сортировки, он может принимать аргумент лямбда в качестве используемого компаратора, позволяя использовать один метод сортировки для простого сравнения различных полей объекта или даже совершенно другого объекта.