Яка різниця в продуктивності між цими наступними петлями?
for (Object o: objectArrayList) {
o.DoSomething();
}
і
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
Яка різниця в продуктивності між цими наступними петлями?
for (Object o: objectArrayList) {
o.DoSomething();
}
і
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
Відповіді:
З пункту 46 " Дієвої Java " Джошуа Блоха:
Цикл «for-every», введений у версії 1.5, позбавляється від захаращення та можливості помилки, повністю заховавши ітератор або змінну індексу. Отримана ідіома однаковою мірою стосується колекцій та масивів:
// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); }
Коли ви побачите товсту кишку (:), прочитайте її як "в". Таким чином, цикл, зазначений вище, означає "для кожного елемента е в елементах". Зауважте, що не застосовується штраф за ефективність використання циклу "для кожного", навіть для масивів. Насправді він може запропонувати невелику перевагу в порівнянні із звичайним циклом для певних обставин, оскільки він обчислює межу індексу масиву лише один раз. Хоча це можна зробити вручну (Пункт 45), програмісти не завжди роблять це.
Усі ці петлі роблять точно так само, я просто хочу їх показати, перш ніж кинути свої два центи.
По-перше, класичний спосіб прокручування списку:
for (int i=0; i < strings.size(); i++) { /* do something using strings.get(i) */ }
По-друге, кращий спосіб, оскільки це менше схильність до помилок (скільки разів ви ВИ зробили "ой, змішали змінні i і j в цих петлях в циклі")?
for (String s : strings) { /* do something using s */ }
По-третє, мікрооптимізований для циклу:
int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }
Тепер фактичні два центи: принаймні, коли я тестував їх, третій був найшвидшим під час підрахунку мілісекунд на те, скільки часу знадобилося для кожного типу циклу, і проста операція в ньому повторилася кілька мільйонів разів - для цього використовується Java 5 з jre1.6u10 для Windows на випадок, коли когось цікавить.
Хоча це, принаймні, здається, що третя найшвидша, ви дійсно повинні запитати себе, чи хочете ви ризикувати впроваджувати цю оптимізацію виводків скрізь у вашому циклі циклу, оскільки з того, що я бачив, фактичне циклічне перетворення t, як правило, найбільш трудомістка частина будь-якої реальної програми (а може, я просто працюю на неправильному полі, хто знає). А також, як я вже згадував у приводі для Java для кожного циклу (деякі посилаються на нього як цикл Iterator, а інші - на цикл for-in ), ви менше шанси натрапити на цю конкретну дурну помилку під час її використання. І перш ніж обговорювати, як це навіть може бути швидше, ніж інші, пам’ятайте, що javac взагалі не оптимізує байт-код (ну, майже взагалі), він просто компілює його.
Якщо ви перебуваєте у мікрооптимізації, хоча та / або у вашому програмному забезпеченні використовується безліч рекурсивних циклів, і тоді вам може бути цікавий тип третього циклу. Просто пам’ятайте, що добре орієнтувати ваше програмне забезпечення як до, так і після зміни циклів для циклів, які ви маєте на цей дивний, мікрооптимізований.
get(int)
, інший використовує Iterator
. Подумайте, LinkedList
де продуктивність for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
набагато гірша, оскільки це робиться в get(int)
n разів.
Як правило, перевага віддається кожному циклу. Підхід "отримати" може бути повільнішим, якщо реалізація списку, яку ви використовуєте, не підтримує випадковий доступ. Наприклад, якщо використовується LinkedList, ви несете прохідну вартість, тоді як для кожного підходу використовується ітератор, який відстежує його позицію в списку. Більше інформації про нюанси для кожного циклу .
Я думаю, стаття зараз тут: нове місце
Посилання, показане тут, було мертвим.
Ну, ефективність роботи в основному незначна, але не дорівнює нулю. Якщо ви подивитеся на RandomAccess
інтерфейс JavaDoc :
Як правило, реалізація списку повинна реалізувати цей інтерфейс, якщо для типових примірників класу цей цикл:
for (int i=0, n=list.size(); i < n; i++) list.get(i);
працює швидше, ніж цей цикл:
for (Iterator i=list.iterator(); i.hasNext();) i.next();
І для-кожного циклу використовується версія з ітератором, так, ArrayList
наприклад, для-кожного циклу не найшвидший.
На жаль, різниця, на жаль, є.
Якщо подивитися на згенерований код байтів для обох типів циклів, вони відрізняються.
Ось приклад з вихідного коду Log4j.
У /log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java у нас є статичний внутрішній клас під назвою Log4jMarker, який визначає:
/*
* Called from add while synchronized.
*/
private static boolean contains(final Marker parent, final Marker... localParents) {
//noinspection ForLoopReplaceableByForEach
for (final Marker marker : localParents) {
if (marker == parent) {
return true;
}
}
return false;
}
Зі стандартним циклом:
private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
Code:
0: iconst_0
1: istore_2
2: aload_1
3: arraylength
4: istore_3
5: iload_2
6: iload_3
7: if_icmpge 29
10: aload_1
11: iload_2
12: aaload
13: astore 4
15: aload 4
17: aload_0
18: if_acmpne 23
21: iconst_1
22: ireturn
23: iinc 2, 1
26: goto 5
29: iconst_0
30: ireturn
З кожним:
private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
Code:
0: aload_1
1: astore_2
2: aload_2
3: arraylength
4: istore_3
5: iconst_0
6: istore 4
8: iload 4
10: iload_3
11: if_icmpge 34
14: aload_2
15: iload 4
17: aaload
18: astore 5
20: aload 5
22: aload_0
23: if_acmpne 28
26: iconst_1
27: ireturn
28: iinc 4, 1
31: goto 8
34: iconst_0
35: ireturn
Що з цим Oracle?
Я спробував це з Java 7 і 8 на Windows 7.
Завжди краще використовувати ітератор, а не індексувати. Це тому, що ітератор, швидше за все, оптимізований для реалізації списку, хоча індексований (виклик get) може не бути. Наприклад, LinkedList - це Список, але індексація через його елементи буде повільнішою, ніж повторення за допомогою ітератора.
foreach робить чіткий намір вашого коду, і це, як правило, перевагу над дуже незначним покращенням швидкості - якщо така є.
Щоразу, коли я бачу індексований цикл, я мушу його проаналізувати трохи довше, щоб переконатися, що він робить те, що я думаю, що це робить. Наприклад, чи починається він з нуля, чи включає він чи виключає кінцеву точку тощо?
Більшість мого часу, здається, витрачається на читання коду (який я написав або хтось інший написав), а чіткість майже завжди важливіша за ефективність. Цього дня легко відмовитись від роботи, оскільки Hotspot робить таку дивовижну роботу.
Наступний код:
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
interface Function<T> {
long perform(T parameter, long x);
}
class MyArray<T> {
T[] array;
long x;
public MyArray(int size, Class<T> type, long x) {
array = (T[]) Array.newInstance(type, size);
this.x = x;
}
public void forEach(Function<T> function) {
for (T element : array) {
x = function.perform(element, x);
}
}
}
class Compute {
int factor;
final long constant;
public Compute(int factor, long constant) {
this.factor = factor;
this.constant = constant;
}
public long compute(long parameter, long x) {
return x * factor + parameter + constant;
}
}
public class Main {
public static void main(String[] args) {
List<Long> numbers = new ArrayList<Long>(50000000);
for (int i = 0; i < 50000000; i++) {
numbers.add(i * i + 5L);
}
long x = 234553523525L;
long time = System.currentTimeMillis();
for (int i = 0; i < numbers.size(); i++) {
x += x * 7 + numbers.get(i) + 3;
}
System.out.println(System.currentTimeMillis() - time);
System.out.println(x);
x = 0;
time = System.currentTimeMillis();
for (long i : numbers) {
x += x * 7 + i + 3;
}
System.out.println(System.currentTimeMillis() - time);
System.out.println(x);
x = 0;
numbers = null;
MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
for (int i = 0; i < 50000000; i++) {
myArray.array[i] = i * i + 3L;
}
time = System.currentTimeMillis();
myArray.forEach(new Function<Long>() {
public long perform(Long parameter, long x) {
return x * 8 + parameter + 5L;
}
});
System.out.println(System.currentTimeMillis() - time);
System.out.println(myArray.x);
myArray = null;
myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
for (int i = 0; i < 50000000; i++) {
myArray.array[i] = i * i + 3L;
}
time = System.currentTimeMillis();
myArray.forEach(new Function<Long>() {
public long perform(Long parameter, long x) {
return new Compute(8, 5).compute(parameter, x);
}
});
System.out.println(System.currentTimeMillis() - time);
System.out.println(myArray.x);
}
}
Дає такий вихід у моїй системі:
224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895
Я використовую Ubuntu 12.10 альфа з оновленням OracleJDK 1.7 6.
Взагалі HotSpot оптимізує безліч непрямих та простих зайвих операцій, тому в цілому вам не варто турбуватися про них, якщо їх багато в послідовності або вони сильно вкладені.
З іншого боку, індексоване потрапляння на LinkedList набагато повільніше, ніж виклик наступного в ітераторі для LinkedList, щоб ви могли уникнути цього показника продуктивності, зберігаючи читабельність при використанні ітераторів (явно чи неявно в кожному циклі).
Навіть із чимось на зразок ArrayList або Vector, де "get" - це простий пошук масиву, другий цикл все ще має додаткові накладні витрати, ніж перший. Я б очікував, що це буде трохи крихітніше, ніж перший.
Єдиний спосіб точно знати - це його порівняння, і навіть це не так просто, як це може здатися . Компілятор JIT може зробити дуже несподівані речі для вашого коду.
Ось короткий аналіз різниці, розроблений командою розробників Android:
https://www.youtube.com/watch?v=MZOf3pOAM6A
Результатом є те , що є різниця, і в дуже обмежених умовах з дуже великими списками це може бути помітна різниця. У їх тестуванні для кожного циклу потрібно було вдвічі більше. Однак їх тестування було проведено на масиві 400 000 цілих чисел. Фактична різниця на елемент у масиві становила 6 мікросекунд . Я не тестував, і вони не говорили, але я б очікував, що різниця буде трохи більшою, використовуючи об'єкти, а не примітиви, але навіть все-таки, якщо ви не будуєте код бібліотеки, де ви не маєте уявлення про масштаб того, про що вас запитають. щоб повторити, я думаю, що різницю не варто наголошувати.
За назвою змінної objectArrayList
я припускаю, що це екземпляр java.util.ArrayList
. У такому випадку різниця в продуктивності була б непомітною.
З іншого боку, якщо це екземпляр java.util.LinkedList
, другий підхід буде набагато повільніше, оскільки List#get(int)
операція O (n).
Отже, перший підхід завжди бажаний, якщо індекс не потрібен логіці в циклі.
1. for(Object o: objectArrayList){
o.DoSomthing();
}
and
2. for(int i=0; i<objectArrayList.size(); i++){
objectArrayList.get(i).DoSomthing();
}
І те й інше, але для легкого та безпечного використання програмування для кожного, є можливості для помилок, схильних до 2-го способу використання.
Дивно, що ніхто не згадував очевидного - foreach виділяє пам'ять (у формі ітератора), тоді як нормальний цикл не виділяє жодної пам'яті. Для ігор на Android це проблема, оскільки це означає, що сміттєзбірник буде періодично працювати. У грі ви не хочете, щоб сміттєзбірник працював ... ВСЕ. Тому не використовуйте петлі foreach у методі малювання (або візуалізації).
Отримана відповідь відповідає на питання, крім виняткового випадку ArrayList ...
Оскільки більшість розробників покладаються на ArrayList (принаймні я так вважаю)
Тож я зобов’язаний додати тут правильну відповідь.
Прямо з документації розробника: -
Покращений цикл (також інколи відомий як цикл "для кожного") може використовуватися для колекцій, що реалізують інтерфейс Iterable, і для масивів. З колекціями виділяється ітератор для здійснення інтерфейсних викликів до hasNext () та next (). З ArrayList рукописний відлічений цикл приблизно в 3 рази швидший (з JIT або без нього), але для інших колекцій розширений синтаксис циклу буде точно еквівалентний явному використанню ітератора.
Існує кілька альтернатив для ітерації через масив:
static class Foo {
int mSplat;
}
Foo[] mArray = ...
public void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray[i].mSplat;
}
}
public void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; ++i) {
sum += localArray[i].mSplat;
}
}
public void two() {
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
}
}
zero () є найповільнішим, оскільки JIT поки не може оптимізувати витрати на отримання довжини масиву один раз за кожну ітерацію через цикл.
one () швидше. Він витягує все з локальних змінних, уникаючи пошуку. Тільки довжина масиву пропонує перевагу продуктивності.
два () - найшвидший для пристроїв, що не мають JIT, і не відрізняються від одного () для пристроїв з JIT. Він використовує розширений синтаксис для циклу, введений у версії 1.5 мови програмування Java.
Таким чином, вам слід використовувати розширений цикл за замовчуванням, але розглянути рукописний відлічений цикл для критичної продуктивності ітерації ArrayList.
Так, for-each
варіант швидше, ніж зазвичай index-based-for-loop
.
for-each
варіант використання iterator
. Тож проїзд швидше, ніж звичайний for
цикл, який базується на індексі.
Це тому iterator
, що оптимізовано для переходу, тому що він вказує безпосередньо перед наступним елементом і відразу після попереднього елемента . Однією з причин того, index-based-for-loop
щоб бути повільним, є те, що він повинен кожного разу обчислювати та переходити до позиції елемента, що не є з iterator
.
public class FirstJavaProgram {
public static void main(String[] args)
{
int a[]={1,2,3,45,6,6};
// Method 1: this is simple way to print array
for(int i=0;i<a.length;i++)
{
System.out.print(a[i]+" ");
}
// Method 2: Enhanced For loop
for(int i:a)
{
System.out.print(i+" ");
}
}
}