Apakah ada perbedaan kinerja antara loop untuk dan untuk-setiap loop?

184

Apa, jika ada, perbedaan kinerja antara dua loop berikut?

for (Object o: objectArrayList) {
    o.DoSomething();
}

dan

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}
eflles
sumber
@ Ketaro: Ini adalah loop "untuk setiap", bukan loop "for-in"
Ande Turner
dalam java disebut "untuk masing-masing", tetapi ketika datang ke Objective C disebut "untuk In" loop.
damithH
Perpanjangan untuk kinerja loop dibahas di sini: stackoverflow.com/questions/12155987/…
eckes
Bahkan jika akan ada perbedaan, itu akan sangat kecil sehingga Anda harus mendukung keterbacaan kecuali potongan kode ini dieksekusi triliunan kali per detik. Dan kemudian Anda akan memerlukan tolok ukur yang tepat untuk menghindari optimasi prematur.
Zabuzard

Jawaban:

212

Dari Butir 46 di Java Efektif oleh Joshua Bloch:

Untuk-setiap loop, diperkenalkan pada rilis 1.5, menghilangkan kekacauan dan kesempatan untuk kesalahan dengan menyembunyikan variabel iterator atau indeks sepenuhnya. Ungkapan yang dihasilkan berlaku sama untuk koleksi dan array:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}

Saat Anda melihat titik dua (:), bacalah "in." Dengan demikian, loop di atas berbunyi "untuk setiap elemen e dalam elemen." Perhatikan bahwa tidak ada penalti kinerja untuk menggunakan setiap loop, bahkan untuk array. Bahkan, itu mungkin menawarkan sedikit keunggulan kinerja dibandingkan loop biasa untuk dalam beberapa keadaan, karena menghitung batas indeks array hanya sekali. Meskipun Anda bisa melakukan ini dengan tangan (Item 45), programmer tidak selalu melakukannya.

Vijay Dev
sumber
48
Layak disebutkan bahwa dalam setiap loop tidak ada cara untuk mengakses penghitung indeks (karena tidak ada)
basszero
Ya, tetapi penghitung itu sekarang terlihat di luar lingkaran. Tentu, ini adalah perbaikan sederhana tetapi begitu juga untuk masing-masing!
Indolering
74
Ada penalti kinerja pengalokasian iterator. Saya memiliki beberapa kode yang sangat paralel dalam wallpaper hidup Android. Saya melihat bahwa pengumpul sampah menjadi gila. Itu karena untuk setiap loop mengalokasikan iterator sementara di banyak thread (berumur pendek), menyebabkan pengumpul sampah banyak pekerjaan. Beralih ke loop berbasis indeks biasa memperbaiki masalah.
gsingh2011
1
@ gsingh2011 Tetapi ini juga tergantung pada apakah Anda menggunakan daftar akses acak atau tidak. Menggunakan akses berbasis indeks ke daftar akses non-acak akan jauh lebih buruk daripada menggunakan untuk-masing-masing dengan daftar akses acak, saya kira. Jika Anda bekerja dengan Daftar antarmuka dan karenanya tidak tahu jenis implementasi yang sebenarnya, Anda dapat memeriksa apakah daftar tersebut adalah instance dari (mengimplementasikan) RandomAccess, jika Anda benar-benar sangat peduli: docs.oracle.com/javase/8 /docs/api/java/util/RandomAccess.html
Puce
3
@ gsingh2011 Dokumen Android ( developer.android.com/training/articles/perf-tips.html#Loops ) menyebutkan bahwa Anda akan memiliki penalti performa saat menggunakan foreach hanya melalui ArrayList saja, bukan koleksi lainnya. Saya ingin tahu apakah itu kasus Anda.
Viccari
29

Semua loop ini melakukan hal yang persis sama, saya hanya ingin menunjukkan ini sebelum memasukkan dua sen saya.

Pertama, cara klasik perulangan melalui Daftar:

for (int i=0; i < strings.size(); i++) { /* do something using strings.get(i) */ }

Kedua, cara yang disukai karena lebih sedikit rawan kesalahan (berapa kali ANDA melakukan "oops, mencampur variabel i dan j dalam loop ini dalam loop" hal?).

for (String s : strings) { /* do something using s */ }

Ketiga, loop mikro yang dioptimalkan:

int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }

Sekarang dua sen aktual: Setidaknya ketika saya menguji ini, yang ketiga adalah tercepat ketika menghitung milidetik pada berapa lama waktu untuk setiap jenis loop dengan operasi sederhana di dalamnya diulang beberapa juta kali - ini menggunakan Java 5 dengan jre1.6u10 pada Windows jika ada yang tertarik.

Walaupun setidaknya tampaknya agar yang ketiga adalah yang tercepat, Anda benar-benar harus bertanya pada diri sendiri apakah Anda ingin mengambil risiko menerapkan optimasi lubang pengintai ini di mana-mana dalam kode pengulangan Anda karena dari apa yang saya lihat, pengulangan aktual bukan t biasanya bagian yang paling memakan waktu dari program nyata (atau mungkin saya hanya bekerja di bidang yang salah, siapa tahu). Dan juga seperti yang saya sebutkan dalam dalih untuk Java untuk-setiap loop (beberapa menyebutnya sebagai loop Iterator dan yang lainnya sebagai for-in loop ) Anda cenderung memukul salah satu bug bodoh tertentu ketika menggunakannya. Dan sebelum memperdebatkan bagaimana ini bahkan bisa lebih cepat daripada yang lain, ingat bahwa javac tidak mengoptimalkan bytecode sama sekali (well, hampir sama sekali toh), itu hanya mengkompilasinya.

Jika Anda ke optimasi mikro dan / atau perangkat lunak Anda menggunakan banyak loop rekursif dan semacamnya maka Anda mungkin tertarik pada tipe loop ketiga. Ingatlah untuk membuat tolok ukur perangkat lunak Anda dengan baik sebelum dan sesudah mengubah for loop Anda harus ke yang aneh ini, dioptimalkan secara mikro.

P. Arrayah
sumber
5
Harap dicatat bahwa for loop dengan ++ i <= size adalah "1-based", mis. Get-method di dalam loop akan dipanggil untuk nilai 1, 2, 3, dll.
volley
15
Cara yang lebih baik untuk menulis loop yang dioptimalkan-mikro adalah untuk (int i = 0, size = strings.size (); ++ i <= size;) {} Ini lebih disukai karena meminimalkan ruang lingkup ukuran
DONal
1
bukan yang ketiga dimulai dari i = 1 pada saat pertama kali melewati loop, melewatkan elemen pertama. dan itu menjadi for for tidak perlu. int n = strings.length; while (n -> 0) {System.out.println ("" + n + "" + string [n]); }
Lassi Kinnunen
1
@ DONal bahwa pola lingkaran melewatkan yang pertama dan memberikan IOOBE. Yang ini berfungsi: untuk (int i = -1, size = list.size (); ++ i <size;)
Nathan Adams
1
"Semua loop ini melakukan hal yang persis sama" tidak benar. Satu menggunakan akses acak get(int), yang lain menggunakan Iterator. Pertimbangkan di LinkedListmana kinerja for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }jauh lebih buruk karena melakukan beberapa get(int)kali.
Steve Kuo
13

Untuk-setiap loop umumnya harus lebih disukai. Pendekatan "dapatkan" mungkin lebih lambat jika implementasi Daftar yang Anda gunakan tidak mendukung akses acak. Misalnya, jika LinkedList digunakan, Anda akan dikenakan biaya traversal, sedangkan pendekatan untuk masing-masing menggunakan iterator yang melacak posisinya dalam daftar. Informasi lebih lanjut tentang nuansa setiap loop .

Saya pikir artikelnya ada di sini: lokasi baru

Tautan yang ditunjukkan di sini sudah mati.

Zach Scrivena
sumber
12

Ya, dampak kinerja sebagian besar tidak signifikan, tetapi tidak nol. Jika Anda melihat RandomAccessantarmuka JavaDoc :

Sebagai aturan praktis, implementasi Daftar harus mengimplementasikan antarmuka ini jika, untuk instance kelas yang khas, loop ini:

for (int i=0, n=list.size(); i < n; i++)
    list.get(i);

berjalan lebih cepat dari loop ini:

for (Iterator i=list.iterator(); i.hasNext();)
      i.next();

Dan untuk-setiap loop menggunakan versi dengan iterator, jadi ArrayListmisalnya, untuk-setiap loop tidak tercepat.

Sarmun
sumber
Benarkah masing-masing? Bahkan satu dengan array? Saya baca di sini stackoverflow.com/questions/1006395/… bahwa itu tidak melibatkan iterator.
Ondra Žižka
@ OndraŽižka: Loop untuk-masing-masing menggunakan iterator ketika mengulangi Iterables, tetapi tidak array. Untuk array digunakan for-loop dengan variabel indeks. Ada informasi tentang ini di JLS .
Lii
ya jadi Anda pertama-tama harus membuatnya menjadi array dengan toArray, memiliki biaya.
Lassi Kinnunen
6

Sayangnya ada perbedaan.

Jika Anda melihat kode byte yang dihasilkan untuk kedua jenis loop, mereka berbeda.

Berikut adalah contoh dari kode sumber Log4j.

Di /log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java kita memiliki kelas dalam statis yang disebut Log4jMarker yang mendefinisikan:

    /*
     * 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;
    }

Dengan loop standar:

  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

Dengan untuk masing-masing:

  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

Ada apa dengan ITU Oracle?

Saya sudah mencoba ini dengan Java 7 dan 8 pada Windows 7.

Gary Gregory
sumber
7
Bagi mereka yang mencoba membaca pembongkaran, hasil akhirnya adalah bahwa kode yang dihasilkan di dalam loop identik, tetapi untuk-setiap setup tampaknya telah menciptakan variabel sementara tambahan yang berisi referensi ke argumen kedua. Jika variabel tersembunyi tambahan terdaftar, tetapi parameter itu sendiri tidak selama pembuatan kode, maka untuk masing-masing akan lebih cepat; jika parameter terdaftar dalam contoh for (;;), waktu eksekusi akan sama. Harus patokan?
Robin Davies
4

Itu selalu lebih baik untuk menggunakan iterator daripada pengindeksan. Ini karena iterator kemungkinan besar dioptimalkan untuk implementasi Daftar sementara indeks (panggilan get) mungkin tidak. Misalnya LinkedList adalah Daftar tetapi pengindeksan melalui elemen-elemennya akan lebih lambat daripada iterasi menggunakan iterator.

Steve Kuo
sumber
10
Saya pikir tidak ada yang namanya "selalu" dalam optimasi kinerja.)
eckes
4

foreach membuat maksud kode Anda lebih jelas dan yang biasanya lebih disukai daripada peningkatan kecepatan yang sangat kecil - jika ada.

Setiap kali saya melihat loop diindeks saya harus menguraikannya sedikit lebih lama untuk memastikan itu melakukan apa yang saya pikirkan Apakah itu mulai dari nol, apakah itu termasuk atau tidak termasuk titik akhir dll?

Sebagian besar waktu saya tampaknya dihabiskan membaca kode (yang saya tulis atau orang lain tulis) dan kejelasan hampir selalu lebih penting daripada kinerja. Sangat mudah untuk mengabaikan kinerja hari ini karena Hotspot melakukan pekerjaan yang luar biasa.

Fortyrunner
sumber
4

Kode berikut:

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);
    }
}

Memberikan output berikut pada sistem saya:

224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895

Saya menjalankan Ubuntu 12.10 alpha dengan pembaruan OracleJDK 1.7 6.

Secara umum HotSpot mengoptimalkan banyak tipuan dan operasi redup sederhana, jadi secara umum Anda tidak perlu khawatir tentang mereka kecuali ada banyak dari mereka di seqence atau mereka sangat bersarang.

Di sisi lain, pengindeksan di LinkedList jauh lebih lambat daripada memanggil iterator berikutnya untuk LinkedList sehingga Anda dapat menghindari kinerja tersebut sambil mempertahankan keterbacaan saat Anda menggunakan iterator (secara eksplisit atau implisit dalam untuk setiap loop).

Wibowit
sumber
3

Bahkan dengan sesuatu seperti ArrayList atau Vector, di mana "get" adalah pencarian array sederhana, loop kedua masih memiliki overhead tambahan yang tidak dimiliki oleh yang pertama. Saya berharap itu menjadi sedikit lebih lambat dari yang pertama.

Paul Tomblin
sumber
Loop pertama harus mendapatkan setiap elemen juga. Ini menciptakan iterator di belakang layar untuk melakukan ini. Mereka benar-benar setara.
Bill the Lizard
Berpikir dalam hal C, sebuah iterator bisa saja menambah pointer, tetapi get harus mengalikan nilai i dengan lebar pointer setiap kali.
Paul Tomblin
Itu tergantung pada jenis daftar yang Anda gunakan. Saya pikir Anda benar, menggunakan get tidak akan pernah lebih cepat, dan kadang-kadang lebih lambat.
Bill the Lizard
3

Satu-satunya cara untuk mengetahui dengan pasti adalah dengan membandingkannya, dan bahkan itu tidak sesederhana kedengarannya . Kompiler JIT dapat melakukan hal-hal yang sangat tidak terduga pada kode Anda.

Jouni K. Seppänen
sumber
3

Berikut adalah analisis singkat tentang perbedaan yang dikeluarkan oleh tim pengembangan Android:

https://www.youtube.com/watch?v=MZOf3pOAM6A

Hasilnya adalah bahwa ada adalah perbedaan, dan dalam lingkungan yang sangat terkendali dengan daftar yang sangat besar itu bisa menjadi perbedaan yang nyata. Dalam pengujian mereka, untuk setiap loop memakan waktu dua kali lebih lama. Namun, pengujian mereka lebih dari daftar array 400.000 bilangan bulat. Perbedaan aktual per elemen dalam array adalah 6 mikrodetik . Saya belum menguji dan mereka tidak mengatakan, tapi saya berharap perbedaannya menjadi sedikit lebih besar menggunakan objek daripada primitif, tetapi bahkan masih kecuali Anda sedang membangun kode perpustakaan di mana Anda tidak tahu skala apa yang akan Anda tanyakan untuk beralih, saya pikir perbedaannya tidak perlu ditekankan.

TBridges42
sumber
2

Dengan nama variabel objectArrayList, saya menganggap itu adalah turunan dari java.util.ArrayList. Dalam hal ini, perbedaan kinerja tidak akan terlalu terasa.

Di sisi lain, jika itu adalah contoh dari java.util.LinkedList, pendekatan kedua akan jauh lebih lambat karena operasi List#get(int)O (n).

Jadi pendekatan pertama selalu lebih disukai kecuali indeks dibutuhkan oleh logika dalam loop.

Changgeng
sumber
1
1. for(Object o: objectArrayList){
    o.DoSomthing();
}
and

2. for(int i=0; i<objectArrayList.size(); i++){
    objectArrayList.get(i).DoSomthing();
}

Keduanya melakukan hal yang sama tetapi untuk penggunaan pemrograman yang mudah dan aman untuk masing-masing, ada kemungkinan rawan kesalahan dalam cara penggunaan kedua.

Santosh
sumber
1

Sungguh aneh bahwa tidak ada yang menyebutkan yang jelas - foreach mengalokasikan memori (dalam bentuk iterator), sedangkan normal untuk loop tidak mengalokasikan memori apa pun. Untuk gim di Android, ini merupakan masalah, karena itu berarti pengumpul sampah akan berjalan secara berkala. Dalam gim Anda tidak ingin pengumpul sampah berjalan ... PERNAH. Jadi jangan gunakan foreach loop dalam metode draw (atau render) Anda.

saraf
sumber
1

Jawaban yang diterima menjawab pertanyaan, selain dari kasus ArrayList yang luar biasa ...

Karena sebagian besar pengembang mengandalkan ArrayList (setidaknya saya percaya begitu)

Jadi saya wajib menambahkan jawaban yang benar di sini.

Langsung dari dokumentasi pengembang: -

Enhanced for loop (juga kadang-kadang dikenal sebagai "for-each" loop) dapat digunakan untuk koleksi yang mengimplementasikan antarmuka Iterable dan untuk array. Dengan koleksi, iterator dialokasikan untuk melakukan panggilan antarmuka ke hasNext () dan next (). Dengan ArrayList, loop terhitung tulisan tangan sekitar 3x lebih cepat (dengan atau tanpa JIT), tetapi untuk koleksi lain, sintaks loop yang ditingkatkan untuk akan sama persis dengan penggunaan iterator eksplisit.

Ada beberapa alternatif untuk iterasi melalui array:

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 () paling lambat, karena JIT belum dapat mengoptimalkan biaya untuk mendapatkan panjang array satu kali untuk setiap iterasi melalui loop.

satu () lebih cepat. Ini menarik semuanya ke dalam variabel lokal, menghindari pencarian. Hanya panjang array yang menawarkan manfaat kinerja.

dua () adalah yang tercepat untuk perangkat tanpa JIT, dan tidak dapat dibedakan dari satu () untuk perangkat dengan JIT. Ini menggunakan sintaks loop ditingkatkan untuk diperkenalkan di versi 1.5 dari bahasa pemrograman Java.

Jadi, Anda harus menggunakan loop yang ditingkatkan untuk secara default, tetapi pertimbangkan loop terhitung tulisan tangan untuk iterasi ArrayList yang kritis terhadap kinerja.

Sreekanth Karumanaghat
sumber
-2

Ya, for-eachvarian lebih cepat dari biasanyaindex-based-for-loop .

for-eachpenggunaan varian iterator. Jadi traverse lebih cepat dari forloop normal yang berbasis indeks.
Ini karena iteratordioptimalkan untuk melintasi, karena menunjuk tepat sebelum elemen berikutnya dan tepat setelah elemen sebelumnya . Salah satu alasan untuk menjadi index-based-for-looplambat adalah itu, ia harus menghitung dan pindah ke posisi elemen setiap kali yang tidak dengan iterator.

cse
sumber
-3
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+" ");
        }
    }
}
SS Tiwari
sumber