Является ли ++ я действительно быстрее, чем я ++ в циклах for в Java?
В Java я обычно делаю цикл for, как показано ниже:
for (int i = 0; i < max; i++) {
something
}
Но недавно коллега напечатал это так:
for (int i = 0; i < max; ++i) {
something
}
Он сказал, что последний будет быстрее. Это правда?
11 ответов
Нет не правда. Вы можете измерить производительность, синхронизируя каждый цикл для большого количества итераций, но я уверен, что они будут одинаковыми.
Миф пришел из C, где ++i
был расценен как быстрее, чем 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 сделают фарш из таких незначительных отличий.
Вы можете использовать обычные методы оптимизации цикла, чтобы получить преимущества в скорости, такие как развертывание, если это применимо.
Декомпилируйте с "javap -c YourClassName" и посмотрите результат и решите из этого. Таким образом, вы видите, что на самом деле делает компилятор в каждом случае, а не то, что вы думаете. Таким образом, вы также видите, почему один путь быстрее, чем другой.
Нет, никакой разницы не будет.
Это пришло из C++, но даже в этом случае не было бы никакой разницы. Где есть разница, где я - объект. i ++ должен был бы сделать дополнительную копию объекта, так как он должен возвращать исходное неизмененное значение элемента, тогда как ++ я могу вернуть измененный объект, чтобы сохранить копию.
В C++ с определенным пользователем объектом стоимость копии может быть значительной, поэтому об этом обязательно стоит помнить. И из-за этого люди склонны использовать его и для переменных типа int, так как в любом случае это так же хорошо...
В Java такой разницы нет. Java-машина взаимодействует с кодом, и независимо от того, пишете ли вы ++ i или i ++, он будет преобразован в байтовый код в точно такой же набор инструкций.
Но в C/C++ есть огромная разница, и если вы не используете какие-либо флаги оптимизации, то ваш цикл может быть медленнее до 3 раз.
Использование таких флагов оптимизации, как -O/-O3, заставит компилятор сделать код сборки вывода проще (в большинстве случаев) и, следовательно, быстрее (в большинстве случаев).
В Java не должно быть никакой разницы - любой современный компилятор* должен генерировать тот же байт-код (просто iinc
) в обоих случаях, поскольку результат выражения приращения не используется напрямую.
Существует третий вариант, все тот же байт-код*:
for (int i = 0; i < max; i += 1) {
something
}
* протестировано с компилятором Eclipse
Даже если бы это было быстрее, никому нет дела во времена HotSpot. Первое, что делает JIT, это удаляет все оптимизации, сделанные javac. После этого все оставлено JIT, чтобы сделать это быстро.