Arhn - архитектура программирования

Почему в определенных ситуациях метод Java Stream.forEach работает быстрее, чем другие циклы?

В настоящее время я занимаюсь проектом, в котором я измеряю скорость различных типов циклов в Java, используя структуру Java Microbenchmark Harness (JMH). Я получил некоторые интересные результаты относительно потоков, которые я не могу объяснить, и мне было интересно, может ли кто-то, кто лучше понимает потоки и списки массивов, помочь мне объяснить мои результаты.

По сути, при переборе списков массивов размером около 100 метод stream.forEach намного быстрее, чем любой другой тип цикла:

Здесь показан график моих результатов: https://i.imgur.com/ypXoWWq.png

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

@Fork(5)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class StackOverflowQ {

    List<Integer> intList;

    int size = 100;

    @Setup
    public void setup() {
        intList = new ArrayList<>(size);
        for(int i = 0; i<size;i++){
            intList.add(i);
        }
    }

    /**
     Work done to each item in the loop.
     */
    public double doWork(int item) {
        return item + 47;
    }

    @Benchmark
    public void standardFor(Blackhole bh){
        for (int i = 0; i<intList.size(); i++){
            bh.consume(doWork(intList.get(i)));
        }
    }

    @Benchmark
    public void streamForEach(Blackhole bh){
        intList.stream().forEach(i -> bh.consume(doWork(i)));
    }

}


Ответы:


1

Этот ответ говорит о java.util.ArrayList. Другие реализации Collection могут (и показывают!) показывать совершенно другие результаты.

Вкратце: потому что потоки используют Spliterator вместо Iterator

Объяснение:

Итерация на основе Iterator основана на вызовах методов hasNext и next, второй из которых изменяет экземпляр Iterator. Это влечет за собой некоторые затраты на производительность. Spliterator поддерживает массовую итерацию. Подробнее об этом читайте здесь. Усовершенствованные циклы for (for (T e : iterable) { ... }), похоже, используют Iterator.

Производительность обычно должна быть второстепенной задачей; вы должны использовать конструкции, которые лучше всего описывают ваши намерения. Хотя из-за причин обратной совместимости это будет сложно, возможно, разница в производительности между разделителем, основанным на forEach, и улучшенным циклом for (на ArrayList) исчезнет в будущем.

Как насчет индексированных циклов for? (List#get(int))
Они показывают худшую производительность, чем сплиттеры, отчасти потому, что им нужно проверить индекс. Другие причины, вероятно, включают вызовы методов, например. для получения данных по индексу, тогда как Spliterator напрямую обращается к массиву. Но это чистое предположение.

Крошечный тест JMH

Ниже вы можете увидеть крохотный бенчмарк, подтверждающий высказанные теории. Обратите внимание, что в идеале бенчмарк должен был работать дольше.

@State(Scope.Benchmark)
@Fork(value = 2)
@Warmup(iterations = 2, time = 3)
@Measurement(iterations = 2, time = 3)
public class A {
    
    public List<Object> list;
    
    @Setup
    public void setup() {
        list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) list.add(i);
    }
    
    @Benchmark
    public void collectionForEach(Blackhole hole) {
        list.forEach(hole::consume);
    }
    
    @Benchmark
    public void iteratorFor(Blackhole hole) {
        for (Iterator<Object> iterator = list.iterator(); iterator.hasNext(); ) {
            hole.consume(iterator.next());
        }
    }
    
    @Benchmark
    public void enhancedFor(Blackhole hole) {
        for (Object e : list) {
            hole.consume(e);
        }
    }
    
    @Benchmark
    public void iteratorForEach(Blackhole hole) {
        list.iterator().forEachRemaining(hole::consume);
    }
    
    @Benchmark
    public void indexedFor(Blackhole hole) {
        for (int i = 0, size = list.size(); i < size; i++) {
            hole.consume(list.get(i));
        }
    }
    
    @Benchmark
    public void streamForEach(Blackhole hole) {
        list.stream().forEach(hole::consume);
    }
    
    @Benchmark
    public void spliteratorForEach(Blackhole hole) {
        list.spliterator().forEachRemaining(hole::consume);
    }
}

И результаты. ops/s означает операций в секунду, чем выше, тем лучше.

Benchmark              Mode  Cnt       Score      Error  Units
A.collectionForEach   thrpt    4  158047.064 ±  959.534  ops/s
A.iteratorFor         thrpt    4  177338.462 ± 3245.129  ops/s
A.enhancedFor         thrpt    4  177508.037 ± 1629.494  ops/s
A.iteratorForEach     thrpt    4  184919.605 ± 1922.114  ops/s
A.indexedFor          thrpt    4  193318.529 ± 2715.611  ops/s
A.streamForEach       thrpt    4  280963.272 ± 2253.621  ops/s
A.spliteratorForEach  thrpt    4  283264.539 ± 3055.967  ops/s
31.12.2020
Новые материалы

Коллекции публикаций по глубокому обучению
Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

Представляем: Pepita
Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

Советы по коду Laravel #2
1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

3 способа решить квадратное уравнение (3-й мой любимый) -
1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

Создание VR-миров с A-Frame
Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

Демистификация рекурсии
КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..