для оптимизации цикла - PullRequest
44 голосов
/ 23 мая 2011
List<String> flowers = new ArrayList<String>();

Мой цикл for в настоящее время выглядит следующим образом ...

for (int i = 0; i < flowers.size(); i++) {
...
}

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

int size = flowers.size();
for (int i = 0; i < size; i++) {
...
}

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

Ответы [ 15 ]

56 голосов
/ 23 мая 2011

Лучше использовать для каждого цикла [более читабельно]

for (Flower flower :flowers){
    //...
}

Я выбросил инструкции, используя javap для следующего кода:

public void forLoop1() {
    List<String> lst = new ArrayList<String>();
    for (int i = 0; i < lst.size(); i++) {
        System.out.println("hi");
    }
}

public void forLoop2() {
    List<String> lst = new ArrayList<String>();
    int size = lst.size();
    for (int i = 0; i < size; i++) {
        System.out.println("hi");
    }
}

public void forLoop1();
  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:  aload_1
   12:  invokeinterface #4,  1; //InterfaceMethod java/util/List.size:()I
   17:  if_icmpge       34
   20:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   23:  ldc     #6; //String hi
   25:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   28:  iinc    2, 1
   31:  goto    10
   34:  return

public void forLoop2();
  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.size:()I
   14:  istore_2
   15:  iconst_0
   16:  istore_3
   17:  iload_3
   18:  iload_2
   19:  if_icmpge       36
   22:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   25:  ldc     #6; //String hi
   27:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   30:  iinc    3, 1
   33:  goto    17
   36:  return

Это не оптимизирует для меня.

Java-версия "1.6.0_22" Java (TM) SE Среда выполнения (сборка 1.6.0_22-b04) Клиентская виртуальная машина Java HotSpot (TM) (сборка 17.1-b03, смешанный режим, обмен)

Так что, если вам нужно выбрать из упомянутых двух, перейдите ко второму, но я лично выберу for-each.


для каждого исполнения

Из пункта 46 в Эффективная Ява Джошуа Блоха:

Цикл for-each, введенный в релиз 1.5, избавляется от беспорядка и возможность для ошибки скрытие итератора или индексной переменной полностью. В результате идиома относится в равной степени к коллекциям и Массивы:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}

Когда вы видите двоеточие (:), читайте его как «В». Таким образом, цикл выше читается как «Для каждого элемента е в элементах». Примечание что нет потери производительности для использования для каждого цикла, даже для массивы. На самом деле, он может предложить небольшое преимущество в производительности по сравнению с обычным для цикла в некоторых обстоятельствах, как это вычисляет предел индекса массива только однажды. Хотя вы можете сделать это рука (пункт 45), программисты не всегда делай так.


Смотрите также

20 голосов
/ 23 мая 2011

Извините, но ответ @ Jigar неверен.Это правильный ответ.(tldr; не используйте for : each).

import java.util.ArrayList;
import java.util.List;

public class LoopTest {

    public static void main(String s[]) {

        long start, end;

        List<Integer> a =  new ArrayList<Integer>();

        for (int i = 0; i < 2500000; i++) {
            a.add(i);
        }

        ///// TESTING FOR : EACH LOOP

        start = System.currentTimeMillis();

        for (Integer j : a) {
            int x = j + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ Integer j : a ] ");

        ////// TESTING DEFAULT LOOP

        start = System.currentTimeMillis();
        for (int i = 0; i < a.size(); i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < a.length; i++ ] ");


        ////// TESTING SLIGHTLY OPTIMIZED LOOP

        start = System.currentTimeMillis();
        int size = a.size();
        for (int i = 0; i < size; i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < size; i++ ] ");        

        //// TESTING MORE OPTIMIZED LOOP

        start = System.currentTimeMillis();
        for (int i = size; --i >= 0;) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = size; --i >= 0; ] ");       

    }

}

Результаты:

96 milli seconds for [ Integer j : a ] 
57 milli seconds for [ int i = 0; i < a.length; i++ ] 
31 milli seconds for [ int i = 0; i < size; i++ ] 
31 milli seconds for [ int i = size; --i >= 0; ] 

Вы можете принять собственное решение, но JVM слишком много приписываетсяоптимизатор.Вы все еще должны быть умными со своим собственным кодом, и использование записи for : each НЕ является хорошей идеей (почти всегда).Как видите, у вас есть хорошая идея, поместив размер в собственную переменную.

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

11 голосов
/ 23 мая 2011

JVM не может оптимизировать его, потому что size() является методом, и JVM не может (и не будет пытаться) определить, что size() всегда будет возвращать одно и то же значение в этом контексте.При условии, что значение size() не изменится, второе немного более производительно, но усиление настолько незначительно, что вам даже не нужно даже думать об его использовании.

9 голосов
/ 23 мая 2011

Если производительность критична, используйте простой счетчик, однако в 98% случаев ясность и простота кода гораздо важнее (например, 1000x или больше), и вам следует использовать цикл for-each.

@ Дэвид отмечает, что использование счетчика происходит быстрее, но я хотел бы отметить, что даже для 10000 записей разница составляет менее микросекунды.

Если у вас есть коллекция из более чем 10 000 записей, весьма вероятно, что вам не следует грубо перебирать каждую возможность. Скорее всего, коллекция с поиском, подобным Map, является лучшим решением для всего, что вы хотели. Если у вас намного меньше 10 000 записей, производительность вряд ли будет важной.

6 голосов
/ 23 мая 2011

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

for (int i = 0, size = flowers.size(); i < size; i++) {
    ...
}
4 голосов
/ 23 мая 2011

Из спецификации языка Java (14.14.1):

Базовый оператор for выполняет некоторый код инициализации, затем выполняет выражение , Statement и некоторый код обновления несколько раз , пока значение Выражения не станет ложным.

Выражение равно i < flowers.size() в вашем первом примере, и оно вычисляется один раз на каждой итерации.В вашем особом случае это не должно иметь заметного значения, потому что flowers.getSize() на ArrayList - очень короткий метод.Но, как правило, если результат выражения одинаков для каждой итерации и является дорогостоящим, выполните предварительное вычисление.

Следствие : это должно привести к одинаковому результату в каждой реализациивиртуальной машины Java и доказывает, что выражение вычисляется один раз за каждую итерацию:

int counter2 = 10;
for (int counter1 = 0; counter1 < counter2; counter1++) {
  System.out.println(counter1 + ", " + counter2);
  counter2--;
}

Вывод:

0, 10
1, 9
2, 8
3, 7
4, 6
3 голосов
/ 27 января 2012

Наилучший вариант -

[ int i = 0; i < size; i++ ]

Ваши результаты будут зависеть от того, какая JVM и другие настройки, например -client vs -server, потому что некоторые измерения настолько малы, что вам нужно использовать наносекунды для измерения,и вам нужно сделать много тестов, в противном случае вы в конечном итоге GC возиться с результатами.Кроме того, такие тесты имеют привычку JVM оптимизировать цикл до нуля.Я попытался устранить этот риск, поместив на экран переменную, которую она изменяет в конце кода.

1.6
-server
7.968242071 milli seconds for [ Integer j : a ] 
7.206275775999999 milli seconds for [ int i = 0; i < a.length; i++ ]  
1.5864E-5 milli seconds for [ int i = 0; i < size; i++ ] 
14.774186076999998 milli seconds for [ int i = size; --i >= 0; ] 

-client
83.36101683999999 milli seconds for [ Integer j : a ] 
44.288568631 milli seconds for [ int i = 0; i < a.length; i++ ]  
2.3191E-5 milli seconds for [ int i = 0; i < size; i++ ] 
24.826621246 milli seconds for [ int i = size; --i >= 0; ] 

1.7

-server
7.029150422 milli seconds for [ Integer j : a ] 
6.6269827779999995 milli seconds for [ int i = 0; i < a.length; i++ ]  
1.3852E-5 milli seconds for [ int i = 0; i < size; i++ ] 
13.842110377 milli seconds for [ int i = size; --i >= 0; ] 
13.868426141 milli seconds for [ int i = a.size()-1; i >= 0; i-- ] 
1.6618000000000003E-5 milli seconds for [ int i = 0; i < a.size(); i++ ] 

-client
7.382479727 milli seconds for [ Integer j : a ] 
6.748068759 milli seconds for [ int i = 0; i < a.length; i++ ]  
1.4162999999999998E-5 milli seconds for [ int i = 0; i < size; i++ ] 
13.951547335999999 milli seconds for [ int i = size; --i >= 0; ] 
13.929234053999998 milli seconds for [ int i = a.size()-1; i >= 0; i-- ] 
1.6873E-5 milli seconds for [ int i = 0; i < a.size(); i++ ] 

Тестовый код:

public static void main(String s[]) {
long start=0, end = 0, delta = 0;
//int[] a = new int[2500000];
List<Integer> a = new ArrayList<Integer>();
int x = 0;

for (int i = 0; i < 2500000; i++) {
    a.add(i);
}

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (Integer j : a) {
         x = j + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ Integer j : a ] ");


start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (int i = 0; i < a.size(); i++) {
         x = a.get(i) + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < a.length; i++ ]  ");

int size = a.size();

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.currentTimeMillis();

    for (int i = 0; i < size; i++) {
         x = a.get(i) + 3;
    }
    end = System.currentTimeMillis();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < size; i++ ] ");

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (int i = size; --i >= 0;) {
         x = a.get(i) + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = size; --i >= 0; ] ");


start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (int i = a.size()-1; i >= 0; i--) {
         x = a.get(i) + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = a.size()-1; i >= 0; i-- ] ");

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.currentTimeMillis();

    for (int i = 0; i < a.size(); i++) {
         x = a.get(i) + 3;
    }
    end = System.currentTimeMillis();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < a.size(); i++ ] ");        

System.out.println(x);
}
2 голосов
/ 05 апреля 2012

Это просто пояснение на примере того, насколько это ситуативно.

Я проверил выполнение «нормального» цикла for (int i = 0; i < list.size(); i++) и микрооптимизированного для цикла for (int i = -1, size = list.size(); ++i < size;). Я запустил оба теста в командной строке и обнаружил ОГРОМНУЮ разницу.

Результаты бега в затмении:

Time for Original: 32552 ms   Time for MicroOptimized 32707 ms
Fastest Loop: Original
Slowest loop takes 0.47616121897272057% more time

Результаты запуска из командной строки:

Time for Original: 274489 ms   Time for MicroOptimized 30516 ms
Fastest Loop: MicroOptimized
Slowest loop takes 799.4920697339101% more time

Таким образом, в eclipse два цикла for занимают одинаковое время, но при запуске из командной строки исходная версия отнимает на 800% больше времени, чем микрооптимизированная версия. Величина разницы поражает меня. Я полагаю, что Eclipse использует другую JVM, которая применяет некоторые хитрые приемы оптимизации.

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

Для полноты, это код, который я запустил:

public static void main(String[] args) {
        List<Byte> list = initializeList();
        byte value = 0;
        final int NUM_LOOPS = 100;

        long startOriginal, startOptimized, endOriginal, endOptimized;

        startOptimized = System.currentTimeMillis();
        for (int j = 0; j < NUM_LOOPS; j++) {
            for (int i = -1, size = list.size(); ++i < size;) {
                value = list.get(i);
            }
        }
        endOptimized = System.currentTimeMillis();

        startOriginal = System.currentTimeMillis();
        for (int j = 0; j < NUM_LOOPS; j++) {
            for (int i = 0; i < list.size(); i++) {
                value = list.get(i);
            }
        }
        endOriginal = System.currentTimeMillis();

        System.out.println(value);
        printResults(startOriginal, endOriginal, startOptimized, endOptimized);
    }

    private static void printResults(long startOriginal, long endOriginal,
            long startOptimized, long endOptimized) {

        long timeOriginal = endOriginal - startOriginal;
        long timeOptimized = endOptimized - startOptimized;

        long diff = Math.abs(timeOriginal - timeOptimized);
        long min = Math.min(timeOriginal, timeOptimized);

        System.out.println("Time for Original: " + timeOriginal + " ms"
                + "   Time for MicroOptimized " + timeOptimized + " ms");

        System.out.println("Fastest Loop: "
                + ((timeOriginal < timeOptimized) ? "Original"
                        : "MicroOptimized"));

        System.out.println("Slowest loop takes " + ((double) 100 * diff / min)
                + "% more time");       
    }

    public static List<Byte> initializeList(){
        List<Byte> list = new ArrayList<Byte>();
        final Byte ONE = new Byte((byte) 1);

        for (int i = 0; i < Integer.MAX_VALUE / 10; i++) {
            list.add(ONE);
        }

        return list;
    }
}
1 голос
/ 28 июня 2017

Кроме того, если вам интересно, имеет ли какое-либо влияние на производительность использование вызова метода в качестве исходной коллекции.То есть - будет ли метод вызываться много раз - ответ - нет.Вот пример:

import java.util.*;
public class TestForeach {
    public static void main (String[] args) {

        for (String s : getStrings()) {
            System.out.println("The string was: "+s);
        }
    } 

    private static List<String> getStrings() {
        System.out.println("IN GET STRINGS");
        return Arrays.asList("A","B","C");
    }
}

Это приведет к:

IN GET STRINGS
The string was: A
The string was: B
The string was: C

Следовательно, метод будет вызываться только один раз.

0 голосов
/ 01 февраля 2018

Простой, но эффективный

 for (ConfigDataModel.VisaTypesBean.AddedBean visatype : visaTypesBeans) {
                            if (visatype.getId() == 24) {

                            }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...