В java я обычно делаю цикл for следующим образом:
for (int i = 0; i < max; i++) {
something
}
Но недавно коллега набрал его так:
for (int i = 0; i < max; ++i) {
something
}
Он сказал, что последнее будет быстрее. Это правда?
В java я обычно делаю цикл for следующим образом:
for (int i = 0; i < max; i++) {
something
}
Но недавно коллега набрал его так:
for (int i = 0; i < max; ++i) {
something
}
Он сказал, что последнее будет быстрее. Это правда?
Нет, это неправда. Вы можете измерить производительность по времени каждого цикла для большого количества итераций, но я уверен, что они будут одинаковыми.
Миф исходил из C, где ++i
считалось быстрее, чем i++
, потому что первое может быть реализовано путем увеличения i, а затем возврата. Последнее можно реализовать, скопировав значение я во временную переменную, увеличив i, а затем вернув временную. Первой версии не нужно делать временную копию, и многие люди предполагают, что она быстрее. Однако, если выражение используется как утверждение, современные компиляторы C могут оптимизировать временную копию, чтобы на практике не было никакой разницы.
Для этого вопроса нужен некоторый код байта Java. Рассмотрим следующий код:
public class PostPre {
public static void main(String args[]) {
int n = 5;
loop1(n);
loop2(n);
}
public static void loop1(int n) {
for (int i = 0; i < n; i++) {}
}
public static void loop2(int n) {
for (int i = 0; i < n; ++i) {}
}
}
Теперь скомпилируйте его и разоберите его:
$ javac PostPre.java; javap -c PostPre.class
Compiled from "PostPre.java"
public class PostPre {
public PostPre();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_5
1: istore_1
2: iload_1
3: invokestatic #2 // Method loop1:(I)V
6: iload_1
7: invokestatic #3 // Method loop2:(I)V
10: return
public static void loop1(int);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iload_0
4: if_icmpge 13
7: iinc 1, 1
10: goto 2
13: return
public static void loop2(int);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iload_0
4: if_icmpge 13
7: iinc 1, 1
10: goto 2
13: return
}
loop1()
и loop2()
имеют одинаковый байтовый код.
Для любого разумного оптимизатора они будут точно такими же. Если вы не уверены, посмотрите на выходной байт-код или профайл.
Даже если это так, что я очень сомневаюсь, вашему коллеге действительно нужно иметь лучшие вещи, чтобы тратить свое время на обучение, чем оптимизировать выражение цикла.
Попробуйте это в своей среде
public class IsOptmized {
public static void main(String[] args) {
long foo; //make sure the value of i is used inside the loop
long now = 0;
long prefix = 0;
long postfix = 0;
for (;;) {
foo = 0;
now = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
foo += i;
}
postfix = System.currentTimeMillis() - now;
foo = 0;
now = System.currentTimeMillis();
for (int i = 0; i < 1000000000; ++i) {
foo += i;
}
prefix = System.currentTimeMillis() - now;
System.out.println("i++ " + postfix + " ++i " + prefix + " foo " + foo);
}
}
}
Моя дает мне
i++ 1690 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1600 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1611 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1600 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1692 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
Итак, даже если это не так, я предполагаю, что есть разница
Это не будет быстрее. Компилятор и JVM с JIT сделают фарш таких незначительных различий.
Вы можете использовать обычные методы оптимизации циклов для получения преимуществ по скорости, например, для разворачивания, если это применимо.
Нет никакой разницы.
Это произошло из С++, но даже там не было бы никакой разницы в этом случае. Там, где есть разница, где я - объект. я ++ должен был бы сделать дополнительную копию объекта, поскольку он должен вернуть исходное неизмененное значение элемента, тогда как ++ я могу вернуть измененный объект, чтобы сохранить копию.
В С++ с пользовательским объектом стоимость копии может быть значимой, поэтому ее обязательно стоит запомнить. И из-за этого люди склонны использовать его и для переменных int, так как это так же хорошо...
В Java не должно быть разницы - любой современный компилятор * должен генерировать одинаковый код байта (просто iinc
) в обоих случаях, так как результат выражения инкремента не используется напрямую.
Существует третий вариант, все тот же байтовый код *:
for (int i = 0; i < max; i += 1) {
something
}
* протестированный с помощью компилятора Eclipse
Декомпилируйте "javap -c YourClassName" и посмотрите результат и решите. Таким образом вы видите, что компилятор действительно делает в каждом случае, а не то, что вы думаете. Таким образом, вы также видите, ПОЧЕМУ один путь быстрее, чем другой.
В Java нет такой разницы. Java-код межсетевых машин, и независимо от того, пишете ли вы ++ я или я ++, он будет преобразован в байтовый код в тот же набор команд.
Но в C/С++ существует огромная разница, и если вы не используете какие-либо флаги оптимизации, то ваш цикл может быть медленнее до 3 раз.
Использование флагов оптимизации, таких как -O/-O3, заставит компилятор сделать код сборки для сборки проще (в большинстве случаев) и, следовательно, быстрее (в большинстве случаев).
Даже это было бы быстрее, никто не заботится в дни HotSpot. Первое, что делает JIT, - это удалить все оптимизации, созданные javac. После этого все остается в JIT, чтобы сделать это быстро.