LINUX.ORG.RU

for vs foreach

 , , ,


0

1

Что работает потенциально быстрее: for или forEach?

for (Object item : items) { ... }
// VS
items.forEach(item -> { ... });

Логика мне подсказывает, что между ними нет разницы (вообще). Интуитивно кажется, что если кто-то и будет быстрее, то for.

Попробовал запустить такой тест:

public class TestApplication {

  public static void main(final String[] args) {
    final List<String> items = new ArrayList<>();
    for (int i = 0; i < 10000000; i++) {
      items.add(String.format("Item #%d", i));
    }

    final AtomicInteger payload = new AtomicInteger();

    // test 1
    long testStart = System.currentTimeMillis();
    for (final String item : items) {
      payload.addAndGet(1);
    }
    long testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 1 (for):     %d", testStop - testStart));

    // test 2
    testStart = System.currentTimeMillis();
    items.forEach(item -> {
      payload.addAndGet(1);
    });
    testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 2 (forEach): %d", testStop - testStart));

    // test 3
    testStart = System.currentTimeMillis();
    for (final String item : items) {
      payload.addAndGet(1);
    }
    testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 3 (for):     %d", testStop - testStart));

    // test 4
    testStart = System.currentTimeMillis();
    items.forEach(item -> {
      payload.addAndGet(1);
    });
    testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 4 (forEach): %d", testStop - testStart));
  }

}

Результат:

Test 1 (for):     171
Test 2 (forEach): 162
Test 3 (for):     162
Test 4 (forEach): 145

(вывод по результатам нескольких запусков: forEach, как правило, работает быстрее for на ~ несколько миллисекунд)

Если убрать payload (запустить пустые циклы), то результат примерно такой (forEach опять быстрее):

Test 1 (for):     76
Test 2 (forEach): 73
Test 3 (for):     78
Test 4 (forEach): 70

Если заменить ArrayList на HashSet, то результаты примерно одинаковые (то for быстрее то forEach):

Test 1 (for):     431
Test 2 (forEach): 421
Test 3 (for):     368
Test 4 (forEach): 386
★★

А ты точно тестируешь for/forEach, а не Эдем?

Я бы для сравнения еще расписал обычный for с индексом, как у тебя в начале.

dave ★★★★★ ()
Ответ на: комментарий от dave

Добавил такой тест:

// test 5
testStart = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
  final String item = items.get(i);
  payload.addAndGet(1);
}
testStop = System.currentTimeMillis();
System.out.println(String.format("Test 5 (for-i):   %d", testStop - testStart));

Результат:

Test 1 (for):     170
Test 2 (forEach): 160
Test 3 (for):     168
Test 4 (forEach): 145
Test 5 (for-i):   171

(что странно - я думал будет на порядок медленнее)

SaBo ★★ ()
Ответ на: комментарий от anonymous

Читайте байткод jvm

Хотелось бы избежать. Я думаю, что где-то есть ответ по поводу работы for vs forEach (в гугле я нашел только информацию о том что нет между ними разницы).

SaBo ★★ ()
Ответ на: комментарий от anonymous

Байткод (но я тут ничего не понимаю):

> adoptopenjdk-11-hotspot-amd64/bin/javap -c -p TestApplication.class
Compiled from "TestApplication.java"
public class com.test.TestApplication {
  public com.test.TestApplication();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: iconst_0
       9: istore_2
      10: iload_2
      11: ldc           #4                  // int 10000000
      13: if_icmpge     45
      16: aload_1
      17: ldc           #5                  // String Item #%d
      19: iconst_1
      20: anewarray     #6                  // class java/lang/Object
      23: dup
      24: iconst_0
      25: iload_2
      26: invokestatic  #7                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      29: aastore
      30: invokestatic  #8                  // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
      33: invokeinterface #9,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      38: pop
      39: iinc          2, 1
      42: goto          10
      45: invokestatic  #10                 // Method java/lang/System.currentTimeMillis:()J
      48: lstore_2
      49: aload_1
      50: invokeinterface #11,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
      55: astore        4
      57: aload         4
      59: invokeinterface #12,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      64: ifeq          82
      67: aload         4
      69: invokeinterface #13,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      74: checkcast     #14                 // class java/lang/String
      77: astore        5
      79: goto          57
      82: invokestatic  #10                 // Method java/lang/System.currentTimeMillis:()J
      85: lstore        4
      87: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
      90: ldc           #16                 // String Test 1 (for):     %d
      92: iconst_1
      93: anewarray     #6                  // class java/lang/Object
      96: dup
      97: iconst_0
      98: lload         4
     100: lload_2
     101: lsub
     102: invokestatic  #17                 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
     105: aastore
     106: invokestatic  #8                  // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
     109: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     112: invokestatic  #10                 // Method java/lang/System.currentTimeMillis:()J
     115: lstore_2
     116: aload_1
     117: invokedynamic #19,  0             // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
     122: invokeinterface #20,  2           // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
     127: invokestatic  #10                 // Method java/lang/System.currentTimeMillis:()J
     130: lstore        4
     132: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
     135: ldc           #21                 // String Test 2 (forEach): %d
     137: iconst_1
     138: anewarray     #6                  // class java/lang/Object
     141: dup
     142: iconst_0
     143: lload         4
     145: lload_2
     146: lsub
     147: invokestatic  #17                 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
     150: aastore
     151: invokestatic  #8                  // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
     154: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     157: return

  private static void lambda$main$0(java.lang.String);
    Code:
       0: return
SaBo ★★ ()
Ответ на: комментарий от SaBo

Читайте байткод jvm

Хотелось бы избежать.

…и продолжать гадать на кофейной гуще, вместо получения самого что ни на есть прямого и достоверного ответа.

JFYI: в IDEA открываешь class-файл – она его декомпилирует.

dimgel ★★★★ ()
Ответ на: комментарий от dimgel

Вот байткод для for:

public class TestApplication {

  public static void main(final String[] args) {
    final List<String> items = new ArrayList<>();
    for (final String item : items) {
      //
    }
  }

}
public class com.test.TestApplication {
  public com.test.TestApplication();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: invokeinterface #4,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
      14: astore_2
      15: aload_2
      16: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
      21: ifeq          37
      24: aload_2
      25: invokeinterface #6,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      30: checkcast     #7                  // class java/lang/String
      33: astore_3
      34: goto          15
      37: return
}

Вот для forEach:

public class TestApplication {

  public static void main(final String[] args) {
    final List<String> items = new ArrayList<>();
    items.forEach(item -> {});
  }

}
public class com.test.TestApplication {
  public com.test.TestApplication();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: invokedynamic #4,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
      14: invokeinterface #5,  2            // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
      19: return

  private static void lambda$main$0(java.lang.String);
    Code:
       0: return
}
SaBo ★★ ()
Ответ на: комментарий от SaBo

А других, без байт-кода, в яве нет методов замера продуктивности выполнения какого-то кода? Ну хотя бы упростить в отдельно-взятом скрипте и замерить?

anonymous ()
Ответ на: комментарий от anonymous

Читайте байткод jvm.

Сижники уже и сюда добрались со своим ассемблером, экономией на спичках и закатом солнца вручную.

Разорванный Флакон

anonymous ()
Ответ на: комментарий от dimgel

JFYI: в IDEA открываешь class-файл – она его декомпилирует.

Толку с того? Она просто for заменяет на итератор. Остальной код такой же (и ничего не понятно).

Т.е. for по факту выглядит так:

Iterator var5 = items.iterator();
while(var5.hasNext()) {
  String item = (String)var5.next();
}
SaBo ★★ ()
Ответ на: комментарий от deep-purple

Менял. forEach быстрее:

Test 2 (forEach): 149
Test 1 (for):     158
Test 4 (forEach): 151
Test 3 (for):     156
SaBo ★★ ()
Ответ на: комментарий от SaBo

И более низкоуровнево уже не посмотреть?

И где тут каст к стрингу как в случае с фор?

deep-purple ★★★★★ ()
Последнее исправление: deep-purple (всего исправлений: 1)

С явой не знаком. Но если речь про оптимизацию кода, то наверное будет иметь смысл, что именно пропускается через эти операторы. Кто мешает просто замерить? Надеюсь в яве есть такая возможность. ИМХО, они должны быть равны по производительности, и упираться только в удобство написания.

anonymous ()

Логика мне подсказывает

Интуитивно кажется

Как будто это разные вещи.

Интуиция — это способ применения предыдущего опыта. Вполне себе валидный логический приём.

WitcherGeralt ★★ ()
Последнее исправление: WitcherGeralt (всего исправлений: 2)
Ответ на: комментарий от WitcherGeralt

Интуиция — это способ применения предыдущего опыта

Идёт как-то чел в зюзю пьяный. И тут прям почувствовал что щас из-за угла два мусора выйдут и заранее свернул поблевать за помойку. Они прошли мимо и его не заметили. А всё потому, что его уже забирали!

deep-purple ★★★★★ ()
Ответ на: комментарий от SaBo

Ну так что там с кастами?

И введи третий вид в тесты (без кастов, если у форича нет кастов):

Iterator x = items.iterator();
while(x.hasNext()) {
  x.next();
  payload.addAndGet(1);
}

deep-purple ★★★★★ ()
Ответ на: комментарий от WitcherGeralt

Интуиция — это способ применения предыдущего опыта. Вполне себе валидный логический приём.

Жаль нет такого в яве. А то было бы прикольнo:

switch (Intuision)
{
    for () {}
    foreach () {}
} 
anonymous ()

Что работает потенциально быстрее: for или forEach?

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

vtVitus ★★★★★ ()
Ответ на: комментарий от deep-purple

forEach опять быстрее:

Test 1 (for):     170
Test 2 (forEach): 150
Test 3 (iterator): 160
Test 4 (for):     162
Test 5 (forEach): 145
Test 6 (iterator): 159
Test 7 (for-i):   148
// test 3
testStart = System.currentTimeMillis();
Iterator<String> iterator = items.iterator();
while (iterator.hasNext()) {
  final String item = iterator.next();
  payload.addAndGet(1);
}
testStop = System.currentTimeMillis();
System.out.println(String.format("Test 3 (iterator): %d", testStop - testStart));

...

// test 6
testStart = System.currentTimeMillis();
iterator = items.iterator();
while (iterator.hasNext()) {
  final String item = iterator.next();
  payload.addAndGet(1);
}
testStop = System.currentTimeMillis();
System.out.println(String.format("Test 6 (iterator): %d", testStop - testStart));

Кстати, IDEA предлагает заменить итератор на for.

SaBo ★★ ()
Ответ на: комментарий от SaBo

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

deep-purple ★★★★★ ()

Кстати, вот дефолтная реализация forEach в java.lang.Iterable:

default void forEach(Consumer<? super T> action) {
  Objects.requireNonNull(action);
  for (T t : this) {
    action.accept(t);
  }
}
SaBo ★★ ()
Ответ на: комментарий от SaBo

А вот реализация forEach в java.util.ArrayList:

public void forEach(Consumer<? super E> action) {
  Objects.requireNonNull(action);
  final int expectedModCount = modCount;
  final Object[] es = elementData;
  final int size = this.size;
  for (int i = 0; modCount == expectedModCount && i < size; i++)
    action.accept(elementAt(es, i));
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
  }
SaBo ★★ ()
Ответ на: комментарий от deep-purple

Так а мне же значение всё равно нужно использовать?..

Но итератор реально быстрее всех предыдущих (и даже for):

Test 1 (for):     169
Test 2 (forEach): 158
Test 3 (iterator): 139
Test 4 (for):     163
Test 5 (forEach): 145
Test 6 (iterator): 138
Test 7 (for-i):   148
// test 3
testStart = System.currentTimeMillis();
Iterator<String> iterator = items.iterator();
while (iterator.hasNext()) {
  iterator.next();
  payload.addAndGet(1);
}
testStop = System.currentTimeMillis();
System.out.println(String.format("Test 3 (iterator): %d", testStop - testStart));

...

// test 6
testStart = System.currentTimeMillis();
iterator = items.iterator();
while (iterator.hasNext()) {
  iterator.next();
  payload.addAndGet(1);
}
testStop = System.currentTimeMillis();
System.out.println(String.format("Test 6 (iterator): %d", testStop - testStart));
SaBo ★★ ()
Ответ на: комментарий от SaBo

Если тест переделать так:

public class TestApplication {

  public static void main(final String[] args) {
    final List<String> items = new ArrayList<>();
    for (int i = 0; i < 10000000; i++) {
      items.add(String.format("Item #%d", i));
    }

    // test 1
    long testStart = System.currentTimeMillis();
    for (final String item : items) {
      item.isEmpty();
    }
    long testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 1 (for):     %d", testStop - testStart));

    // test 2
    testStart = System.currentTimeMillis();
    items.forEach(item -> {
      item.isEmpty();
    });
    testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 2 (forEach): %d", testStop - testStart));

    // test 3
    testStart = System.currentTimeMillis();
    Iterator<String> iterator = items.iterator();
    while (iterator.hasNext()) {
      iterator.next().isEmpty();
    }
    testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 3 (iterator): %d", testStop - testStart));

    // test 4
    testStart = System.currentTimeMillis();
    for (final String item : items) {
      item.isEmpty();
    }
    testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 4 (for):     %d", testStop - testStart));

    // test 5
    testStart = System.currentTimeMillis();
    items.forEach(item -> {
      item.isEmpty();
    });
    testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 5 (forEach): %d", testStop - testStart));

    // test 6
    testStart = System.currentTimeMillis();
    iterator = items.iterator();
    while (iterator.hasNext()) {
      iterator.next().isEmpty();
    }
    testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 6 (iterator): %d", testStop - testStart));

    // test 7
    testStart = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++) {
      items.get(i).isEmpty();
    }
    testStop = System.currentTimeMillis();
    System.out.println(String.format("Test 7 (for-i):   %d", testStop - testStart));
  }

}

То forEach опять выигрывает:

Test 1 (for):     112
Test 2 (forEach): 94
Test 3 (iterator): 127
Test 4 (for):     115
Test 5 (forEach): 99
Test 6 (iterator): 106
Test 7 (for-i):   102

=========

Test 1 (for):     90
Test 2 (forEach): 85
Test 3 (iterator): 103
Test 4 (for):     97
Test 5 (forEach): 80
Test 6 (iterator): 91
Test 7 (for-i):   92
SaBo ★★ ()
Ответ на: комментарий от SaBo

суть в том, что, я полагаю, форич только передает следуюший айтем, без его кастов и юза, поэтому сейчас итератор без кастов и юза оказался впереди.

а теперь добавь в форич использование айтема с кастом, как это было ранее в итераторе, и замерь.

deep-purple ★★★★★ ()
Ответ на: комментарий от SaBo

но честно, мне уже не интересно рассматривать эти спички - экономия уже ясна.

deep-purple ★★★★★ ()
Ответ на: комментарий от SaBo

Ну вообще, foreach для операций по перечислению, а for для инкрементации. Не совсем одно и то же. Поэтому применяй по назначению. Может не прав.

anonymous ()

В общем, итог, по-моему следующий:

Производительность for (iterator) vs forEach зависит от фактической реализации forEach и итератора.

Для реализации java.util.ArrayList forEach выигрывает в производительности у for. Если для java.util.ArrayList заменить forEach дефолтной реализацией из java.lang.Iterable:

public static class TestList<E> extends ArrayList<E> {
    private static final long serialVersionUID = 4967704191406430634L;

    public void forEach(Consumer<? super E> action) {
      Objects.requireNonNull(action);
      for (E t : this) {
        action.accept(t);
      }
    }
  }

То вопроса о том что быстрее быть не может (т.к. используется for / iterator). Результаты теста:

Test 1 (for):     94
Test 2 (forEach): 102
Test 3 (iterator): 95
Test 4 (for):     95
Test 5 (forEach): 102
Test 6 (iterator): 102
Test 7 (for-i):   100
SaBo ★★ ()
Последнее исправление: SaBo (всего исправлений: 1)
Ответ на: комментарий от SaBo

В forEach один раз проверяется граница, внутри итератора. В доступе по индексу она проверяется и в самом условии цикла и в get() списка.

Но по идее оптимизатор такое должен убирать легко

vertexua ★★★★★ ()
Ответ на: комментарий от SaBo

Тесты - говно?

Конечно. Сначала надо java прогреть, потом запустить один тест. Потом ещё раз со вторым, потом повторить, набрать хотя бы 10 тестов, посчитать среднее. Потом подумать и понять что замеры от таких синтетических тестов говорят чуть больше чем ни о чём. Потому что на разной логике или размере выполняемого блока возможно будут совсем другие цифры.

vtVitus ★★★★★ ()
Ответ на: комментарий от anonymous

Большинство определений, которые гуглятся, с тобой не согласны.

WitcherGeralt ★★ ()
Ответ на: комментарий от WitcherGeralt

Это JIT

Это наверное компилятор? Тогда это суд последней инстанции.

anonymous ()

Ты уверен, что там ничего не оптимизируется?

payload можно просто выкинуть и ничего не делать же. Попробуй его хотя бы вывести на экран в конце. Да и тестики поинтересней надо, а не прогон 4 раза по 10000000. Может оно там за сценой всего один раз прогоняет или вообще не прогоняет, так как результат не используется?

anonymous ()

Думаю

  • измерять надо при помощи JMH
  • forEach скорее всего так хорошо оптимизируется из-за Jit. В реальной программе будет много разнообразных вызовов forEach и из-за этого эффективность оптимизиции снизится (см https://shipilev.net/jvm/anatomy-quarks/16-megamorphic-virtual-calls/)
maxcom ★★★★★ ()

for быстрей. В forEach будет виртуальный вызов Consumer.accept.

Legioner ★★★★★ ()
Последнее исправление: Legioner (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.