2012-03-19 4 views
0

У меня есть эта петлякакой цикл быстрее?

for (it= someCollection.iterator; it.hasNext();) 
{ 
    //some code here 
} 

Я изменил его:

for (it= someCollection.iterator;;) 
{ 
    if (!it.hasNext()) 
     break; 
    //some code here 
} 

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

+1

Просто для удовольствия, также попробуйте 'for (Object item: someCollection) {/ * Некоторое число здесь * /}' –

+0

Я думаю, что вы будете бороться, чтобы определить это, поскольку это может зависеть от JVM, машины и т. Д. И т. Д. –

+0

Что вызвало вы хотите узнать, «что было быстрее»? –

ответ

3

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

Используя этот пример кода:

for (Iterator it = c.iterator(); it.hasNext();) { 
     System.out.println(it.next()); 
    } 
    System.out.println("Out"); 

Вы бы получить следующий график потока управления блоком. Я вернул эквивалентный байт-код в источник для удобочитаемости, но все инструкции, созданные System.out.println(it.next());, принадлежат одному блоку, так как вы не можете прыгать посередине или выбраться из него.

Loop Control Flow Diagram

Если вы проверить compiler book, вы обнаружите, что it.hasNext() доминирует System.out.println(it.next()), потому что вы должны пройти через it.hasNext() ехать в System.out.println(it.next()). Край от System.out.println(it.next()) до it.hasNext() называется back-edge, потому что он связывает узел с одним из его доминантов. Это то, что формально определяет, что такое цикл. Первый оператор в for -loop (Iterator it = c.iterator()) фактически не принадлежит к циклу. Нет никакой разницы с циклом while, которому предшествует этот оператор, за исключением области объявленной переменной, но это не имеет значения после компиляции. Первый блок (it.hasNext()) - это заголовок цикла.

Второй пример, как это будет производить тот же график:

for (Iterator it = c.iterator();;) { 
     if (!it.hasNext()) { 
      break; 
     } 
     System.out.println(it.next()); 
    } 
    System.out.println("Out"); 

Основное отличие состоит в том, что могут быть некоторые бесполезные goto заявления, в зависимости от стратегии компиляции.

Если посмотреть на сгенерированный байткод, используя javap -c для этих двух примеров, вы получите это (это был скомпилирован с javac, вы можете получить что-то немного другое, если вы компиляции с компилятором затмений, например):

public void loop1(); 
    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: invokevirtual #4; //Method java/util/ArrayList.iterator:()Ljava/util/Iterator; 
    12: astore_2 
    13: aload_2 
    14: invokeinterface #5, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 
    19: ifeq 37 
    22: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream; 
    25: aload_2 
    26: invokeinterface #7, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 
    31: invokevirtual #8; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
    34: goto 13 
    37: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream; 
    40: ldC#9; //String Out 
    42: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 
    45: return 

public void loop2(); 
    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: invokevirtual #4; //Method java/util/ArrayList.iterator:()Ljava/util/Iterator; 
    12: astore_2 
    13: aload_2 
    14: invokeinterface #5, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 
    19: ifne 25 
    22: goto 40 
    25: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream; 
    28: aload_2 
    29: invokeinterface #7, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 
    34: invokevirtual #8; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
    37: goto 13 
    40: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream; 
    43: ldC#9; //String Out 
    45: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 
    48: return 

Единственное отличие состоит в том, что первый использует ifeq 37, чтобы перейти прямо в конец или перейти к следующему блоку (22), тогда как другой использует ifne, чтобы перейти к блоку после goto (25, что эквивалентно 22 в другое) и использует goto, чтобы перейти в конец в противном случае. Это фактически эквивалентно, и современный JIT-компилятор должен без проблем оптимизировать эту небольшую разницу. Кроме того, ваши две петли точно такие же.

Я не уверен, как вы сделали свои измерения, но вы также должны знать, что это не потому, что System.nanoTime() дает вам результат в наносекундах, что он имеет разрешение этого порядка, вдалеке от этого. Таймеры с высоким разрешением довольно сложно реализовать и будут зависеть от оборудования и ОС. См JavaDoc:

Этот метод обеспечивает точность наносекунд, но не обязательно разрешение наносекунд (то есть, как часто изменяется значение) - не гарантии не принимаются, за исключением, что разрешение по крайней мере так же хорошо, как , что из currentTimeMillis().

Скорее всего, если вы не получите достаточно высокой разницы, вы не получите ничего существенного по сравнению с разрешением.

2

Я бы ожидал, что они скомпилируются в одинаковый байт-код.

+0

Хммм, поэтому нет смысла пытаться оптимизировать этот цикл, я думаю, – Mansuro

0

Они должны выполнить то же самое время.

Что имеется?

while(it.hasNext()) 
+0

ну, нет никакой разницы между различными java-циклами AFAIK, компилятор должен позаботиться об этом – Mansuro

0

два примера являются точно такими же. Примечание: поскольку в циклах нет инструкции по обновлению i.e что-то вроде it.next(), обе петли, вероятно, будут работать вечно, если только у них нет элементов. Или, если вы разместите их здесь, только для иллюстрации.

+0

Можете ли вы объяснить, как вы пришли к этому решению? –

+0

Как объяснялись в предыдущих комментариях .. цикл состоит из 4 шагов -инициализация: которая выполняется только один раз, перед циклом начинает 2-проверка состояние 3-выполнение код 4-обновление здесь инициализация 'it = someCollection.iterator' Условие check is' it.hasNext() ' и не обновлено , разница в том, что кодер переместил проверку состояния от тела цикла до начала тела цикла, который компилируется в такой же код. – zeacuss

0

Синтаксис цикла

for(initialization; Boolean_expression; update) 
{ 
    //Statements 
} 
  1. Этап инициализации выполняется первым, и только один раз. Этот шаг позволяет объявлять и инициализировать любые переменные управления контуром. Вы не обязаны указывать здесь заявление, пока появляется точка с запятой.

  2. Далее вычисляется булево выражение. Если это правда, выполняется тело цикла. Если он является ложным, тело цикла не выполняется, и поток управляющих переходов переходит к следующему утверждению за циклом for.

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

  4. Булево выражение теперь оценивается снова. Если это так, цикл выполняется и процесс повторяется (тело цикла, затем шаг обновления, затем логическое выражение). После того как логическое выражение ложно, цикл for завершается.

В вашем втором коде 4-й шаг не выполняется, если условие выполнено. так что он быстрее, чем 1-й код.

+0

В примерах в вопросе нет выражения «update». – Bruno

+0

@Bruno Я знаю об этом, я говорю о синтаксисе цикла. –

+0

Ваши шаги 3 и 4 не будут выполняться ни в одном из примеров: он выходит из цикла. – Bruno