Varsa, aşağıdaki iki döngü arasındaki performans farkı nedir?
for (Object o: objectArrayList) {
o.DoSomething();
}
ve
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
Varsa, aşağıdaki iki döngü arasındaki performans farkı nedir?
for (Object o: objectArrayList) {
o.DoSomething();
}
ve
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
Yanıtlar:
Joshua Bloch'un Etkili Java'sındaki Madde 46'dan :
1.5 sürümünde tanıtılan for-each döngü, yineleyiciden ve yineleyici veya dizin değişkenini tamamen gizleyerek hata fırsatından kurtulur. Ortaya çıkan deyim, koleksiyonlar ve diziler için eşit olarak geçerlidir:
// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); }
İki nokta üst üste işaretini (:) gördüğünüzde, "inç" olarak okuyun. Böylece, yukarıdaki döngü “elemanlardaki her e öğesi için” olarak okunur. Diziler için bile, her biri için for döngüsünü kullanmanın performans cezası olmadığını unutmayın. Aslında, dizi endeksinin sınırını sadece bir kez hesapladığı için, bazı durumlarda sıradan bir döngü için hafif bir performans avantajı sunabilir. Bunu elinizle yapabilirsiniz (Madde 45), programcılar bunu her zaman yapmaz.
Tüm bu döngüler aynısını yapıyor, sadece iki sentime atmadan önce bunları göstermek istiyorum.
İlk olarak, List'te döngü yapmanın klasik yolu:
for (int i=0; i < strings.size(); i++) { /* do something using strings.get(i) */ }
İkincisi, daha az hataya yatkın olduğu için tercih edilen yol (kaç kez SİZİN "oops, döngüler içindeki bu döngülerdeki i ve j değişkenlerini karıştırdık" şeyini kaç kez yaptınız?).
for (String s : strings) { /* do something using s */ }
Üçüncü olarak, döngü için mikro optimize edilmiş:
int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }
Şimdi gerçek iki sent: En azından bunları test ederken, üçüncüsü, her bir döngü türü için birkaç milyon kez tekrarlanan basit bir işlemle milisaniye sürdüğü zaman en hızlıydı - bu Java 5'i kullanıyordu. Herkes ilgilenirse Windows üzerinde jre1.6u10 ile.
En azından üçüncü olan en hızlı olacak gibi görünse de, gerçekten kendinize bu peephole optimizasyonunu döngü kodunuzdaki her yerde uygulama riskini almak isteyip istemediğinizi sormalısınız, çünkü gerçek döngü değil ' t Genellikle herhangi bir gerçek programın en çok zaman alan kısmı (veya belki de yanlış alanda çalışıyorum, kim bilir) Ayrıca, her bir döngü için Java'nın bahanesinde de bahsettiğim gibi (bazıları Iterator döngüsü ve diğerleri için de döngü olarak adlandırılır ) kullanırken bu belirli aptal hatayı vurma olasılığınız daha düşüktür. Ve bunun bile diğerlerinden bile daha hızlı olabileceğini tartışmadan önce, javacın bayt kodunu hiç optimize etmediğini (neredeyse, neredeyse her durumda) unutmayın.
Yine de mikro optimizasyon yapıyorsanız ve / veya yazılımınız çok sayıda özyinelemeli döngü kullanıyorsa ve bu durumda üçüncü döngü türüyle ilgilenebilirsiniz. Sadece bu tek, mikro optimize edilmiş döngüye sahip olduğunuz döngüleri değiştirmeden önce ve sonra yazılımınızı iyi bir şekilde karşılaştırmayı unutmayın.
get(int)
, diğeri an Iterator
. N kez LinkedList
yaptığı için performansının for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
çok daha kötü olduğunu düşünün get(int)
.
Her biri için döngü genel olarak tercih edilmelidir. Kullandığınız Liste uygulaması rasgele erişimi desteklemiyorsa "get" yaklaşımı daha yavaş olabilir. Örneğin, LinkedList kullanılırsa, geçiş maliyetine maruz kalırsınız, buna karşılık her yaklaşım için listedeki konumunu izleyen bir yineleyici kullanılır. Her bir döngü için nüanslar hakkında daha fazla bilgi .
Bence makale şimdi burada: yeni konum
Burada gösterilen bağlantı ölmüştü.
Performans etkisi çoğunlukla önemsizdir, ancak sıfır değildir. RandomAccess
Arayüzün JavaDoc'una bakarsanız :
Genel bir kural olarak, List uygulamasının, sınıfın tipik örnekleri için bu döngü aşağıdaki durumlarda bu arabirimi uygulamalıdır:
for (int i=0, n=list.size(); i < n; i++) list.get(i);
bu döngüden daha hızlı çalışır:
for (Iterator i=list.iterator(); i.hasNext();) i.next();
Ve for-each döngü yineleyicili sürüm kullanıyor, bu nedenle ArrayList
örneğin for-loop en hızlı değil.
Maalesef bir fark var gibi görünüyor.
Her iki döngü türü için oluşturulan bayt koduna bakarsanız, bunlar farklıdır.
İşte Log4j kaynak kodundan bir örnek.
/Log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java'da Log4jMarker adında statik bir iç sınıfımız var.
/*
* 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;
}
Standart döngü ile:
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
Her biri için:
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
THAT Oracle'ın durumu nedir?
Bunu Windows 7'de Java 7 ve 8 ile denedim.
İndeksleme yerine yineleyiciyi kullanmak her zaman daha iyidir. Bunun nedeni, dizin oluşturucu (get çağrısında bulunma) olmayabilirken yineleyicinin büyük olasılıkla Liste uygulaması için en iyi duruma getirilmesidir. Örneğin LinkedList bir Liste'dir, ancak öğeleri arasında endeksleme yineleyici kullanılarak yinelemekten daha yavaş olacaktır.
foreach, kodunuzun amacını daha net hale getirir ve normalde çok küçük bir hız iyileştirmesi yerine tercih edilir - eğer varsa.
Ne zaman indeksli bir döngü görüyorum ben onun ne düşündüğünü yapar emin olmak için biraz daha ayrıştırmak gerekir Eg sıfırdan başlıyor, bitiş noktası dahil veya hariç mi?
Zamanımın çoğunu kod okumak için harcadığım anlaşılıyor (yazdığım veya başka birisinin yazdığı) ve netlik neredeyse her zaman performanstan daha önemlidir. Hotspot böylesine inanılmaz bir iş çıkardığı için bugünlerde performansı kapatmak kolay.
Aşağıdaki kod:
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);
}
}
Sistemimde aşağıdaki çıktıyı verir:
224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895
OracleJDK 1.7 güncelleme 6 ile Ubuntu 12.10 alfa çalıştırıyorum.
Genel olarak HotSpot birçok indirimi ve basit reduntant operasyonunu optimize eder, bu nedenle genellikle çok fazla seqence yoksa veya çok fazla iç içe olmadıkça bunlar hakkında endişelenmemelisiniz.
Öte yandan, LinkedList için dizinlenmiş get, LinkedList için yineleyicide bir sonraki çağrıdan çok daha yavaştır, bu nedenle yineleyicileri (her döngü için açıkça veya dolaylı olarak) kullanırken okunabilirliği korurken bu performans isabetinden kaçınabilirsiniz.
"Get" ifadesinin basit bir dizi araması olduğu bir ArrayList veya Vector gibi bir şeyle bile, ikinci döngü hala birincisinin sahip olmadığı ek yüke sahiptir. İlkinden biraz daha yavaş olmasını beklerdim.
Kesin olarak bilmenin tek yolu onu karşılaştırmaktır ve bu bile göründüğü kadar basit değildir . JIT derleyicisi kodunuza çok beklenmedik şeyler yapabilir.
İşte Android geliştirme ekibi tarafından ortaya konulan farkın kısa bir analizi:
https://www.youtube.com/watch?v=MZOf3pOAM6A
Sonuç orada olmasıdır olan fark, ve çok büyük listeleri ile çok ölçülü ortamlarda kayda değer bir değişiklik olabilir. Testlerinde, her döngü için iki kat daha uzun sürdü. Bununla birlikte, testleri 400.000 tamsayı içeren bir arraylist üzerinde yapıldı. Dizideki eleman başına gerçek fark 6 mikrosaniyeydi . Test etmedim ve söylemediler, ama farkın ilkellerden ziyade nesneleri kullanarak biraz daha büyük olmasını beklerdim, ama yine de size sorulan şeyin ölçeği hakkında hiçbir fikriniz olmayan bir kütüphane kodu inşa etmedikçe bile yinelemek için, farkın üzerinde durmaya değer olmadığını düşünüyorum.
Değişken adına göre objectArrayList
, bunun bir örneği olduğunu varsayıyorum java.util.ArrayList
. Bu durumda, performans farkı fark edilmeyecektir.
Öte yandan, eğer bir örnekse java.util.LinkedList
, ikinci yaklaşım List#get(int)
O (n) işlemi olduğu için çok daha yavaş olacaktır .
Dolayısıyla indeks döngüdeki mantık için gerekli olmadıkça ilk yaklaşım her zaman tercih edilir.
1. for(Object o: objectArrayList){
o.DoSomthing();
}
and
2. for(int i=0; i<objectArrayList.size(); i++){
objectArrayList.get(i).DoSomthing();
}
Her ikisi de aynısını yapar, ancak her biri için kolay ve güvenli programlama kullanımı için, 2. kullanım yolunda hataya eğilimli olasılıklar vardır.
Hiç kimsenin bariz olandan bahsetmemesi gariptir - foreach bellek (yineleyici şeklinde) ayırır, oysa normal for döngüsü herhangi bir bellek ayırmaz. Android'deki oyunlar için bu bir sorundur, çünkü çöp toplayıcının periyodik olarak çalışacağı anlamına gelir. Bir oyunda çöp toplayıcının çalışmasını istemiyorsunuz ... HİÇ. Bu nedenle, çizim (veya oluşturma) yönteminizde foreach döngülerini kullanmayın.
Kabul edilen cevap, ArrayList'in istisnai durumu dışında soruyu cevaplar ...
Çoğu geliştirici ArrayList'e güvendiğinden (en azından buna inanıyorum)
Bu yüzden buraya doğru cevabı eklemek zorundayım.
Doğrudan geliştirici belgelerinden: -
Geliştirilmiş döngü (bazen "her biri için" döngü olarak da bilinir), Yinelenebilir arabirimi uygulayan koleksiyonlar ve diziler için kullanılabilir. Koleksiyonlarda, hasNext () ve next () öğesine arayüz çağrıları yapmak için bir yineleyici ayrılır. Bir ArrayList ile elle yazılmış sayılan döngü yaklaşık 3 kat daha hızlıdır (JIT ile veya JIT olmadan), ancak diğer koleksiyonlar için geliştirilmiş döngü sözdizimi, açık yineleyici kullanımına tam olarak eşdeğer olacaktır.
Bir dizi üzerinden yineleme yapmak için birkaç alternatif vardır:
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 () en yavaştır, çünkü JIT döngü boyunca her yineleme için dizi uzunluğunu alma maliyetini henüz optimize edemez.
one () daha hızlıdır. Aramalardan kaçınarak her şeyi yerel değişkenlere çeker. Yalnızca dizi uzunluğu bir performans avantajı sunar.
two (), bir JIT olmayan cihazlar için en hızlı ve bir () üzerinden JIT olan cihazlar için ayırt edilemez. Java programlama dilinin 1.5 sürümünde tanıtılan gelişmiş döngü sözdizimini kullanır.
Bu nedenle, varsayılan olarak gelişmiş for döngüsünü kullanmalısınız, ancak performans açısından kritik ArrayList yinelemesi için elle yazılmış sayılan bir döngüyü göz önünde bulundurun.
Evet, for-each
varyant normalden daha hızlı index-based-for-loop
.
for-each
varyant kullanır iterator
. Böylece çaprazlama, for
dizin tabanlı normal döngüden daha hızlıdır .
Bunun nedeni iterator
, geçiş için optimize edilmiş olmasıdır, çünkü bir sonraki öğeden hemen önce ve bir önceki öğeden hemen sonra işaret eder . index-based-for-loop
Yavaş olmanın nedenlerinden biri, her seferinde olmayan eleman konumunu hesaplamak ve hareket ettirmek zorunda olmasıdır iterator
.