Самый быстрый способ итерации массива в Java: переменная цикла vs усиленная для оператора

В Java, быстрее итерации через массив старомодным способом,

for (int i = 0; i < a.length; i++)
    f(a[i]);

Или используя более сжатую форму,

for (Foo foo : a)
    f(foo);

Для ArrayList ответ является тем же самым?

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

Ответ 1

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

Например, рассмотрим этот код:

public static void main(String[] args)
{
    for (String x : args)
    {
        System.out.println(x);
    }
}

При декомпиляции с помощью javap -c Test мы получаем (для метода main):

public static void main(java.lang.String[]);
  Code:
   0:   aload_0
   1:   astore_1
   2:   aload_1
   3:   arraylength
   4:   istore_2
   5:   iconst_0
   6:   istore_3
   7:   iload_3
   8:   iload_2
   9:   if_icmpge   31
   12:  aload_1
   13:  iload_3
   14:  aaload
   15:  astore  4
   17:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   20:  aload   4
   22:  invokevirtual   #3; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   25:  iinc    3, 1
   28:  goto    7
   31:  return

Теперь измените его на использование явного доступа к массиву:

public static void main(String[] args)
{
    for (int i = 0; i < args.length; i++)
    {
        System.out.println(args[i]);
    }
}

Это декомпилирует:

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   aload_0
   4:   arraylength
   5:   if_icmpge   23
   8:   getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   11:  aload_0
   12:  iload_1
   13:  aaload
   14:  invokevirtual   #3; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   17:  iinc    1, 1
   20:  goto    2
   23:  return

В расширенном цикле есть еще немного кода настройки, но они в основном делают то же самое. Нет итераторов. Кроме того, я ожидаю, что они получат JITted еще более похожий код.

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

Ответ 2

Это прямо падает на арену микро-оптимизация. Это действительно неважно. Стилистически я всегда предпочитаю второй, потому что он более краткий, если вам не нужен счетчик циклов для чего-то другого. И это гораздо важнее, чем такая микро-оптимизация: читаемость.

Говоря об этом, для ArrayList не будет большой разницы, но LinkedList будет намного эффективнее со вторым.

Ответ 3

Измерьте это. Ответ на все вопросы производительности может зависеть от VM-версии, процессора, скорости памяти, кэшей и т.д. Таким образом, вы должны измерить его для своей конкретной платформы.

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

Ответ 5

В массиве или коллекции RandomAccess вы можете получить небольшое увеличение скорости:

List<Object> list = new ArrayList<Object>();

for (int i=0, d=list.size(); i<d; i++) {
    something(list.get(i));
}

Но я бы не стал волноваться в целом. Оптимизации, подобные этому, не будут отличаться от вашего кода более чем на 0,1%. Попробуйте использовать java с -prof, чтобы узнать, где ваш код действительно тратит свое время.

Ответ 6

Еще быстрее использовать ParallelArray структуры fork-join (если у вас достаточно большой набор данных).