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();
}
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();
}
Jawaban:
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.
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.
get(int)
, yang lain menggunakan Iterator
. Pertimbangkan di LinkedList
mana kinerja for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
jauh lebih buruk karena melakukan beberapa get(int)
kali.
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.
Ya, dampak kinerja sebagian besar tidak signifikan, tetapi tidak nol. Jika Anda melihat RandomAccess
antarmuka 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 ArrayList
misalnya, untuk-setiap loop tidak tercepat.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
Ya, for-each
varian lebih cepat dari biasanyaindex-based-for-loop
.
for-each
penggunaan varian iterator
. Jadi traverse lebih cepat dari for
loop normal yang berbasis indeks.
Ini karena iterator
dioptimalkan untuk melintasi, karena menunjuk tepat sebelum elemen berikutnya dan tepat setelah elemen sebelumnya . Salah satu alasan untuk menjadi index-based-for-loop
lambat adalah itu, ia harus menghitung dan pindah ke posisi elemen setiap kali yang tidak dengan iterator
.