Mengapa for loop lebih efisien daripada untuk setiap loop di Unity?

8

Selama pembicaraan Persatuan tentang kinerja, orang itu mengatakan kepada kami untuk menghindari for eachloop untuk meningkatkan kinerja dan menggunakan normal untuk loop sebagai gantinya. Apa perbedaan antara fordan for eachdalam sudut pandang implementasi? Apa yang membuat for eachtidak efisien?

Emad
sumber
1
Selain menghitung berapa banyak elemen yang ada dalam array atau apa pun yang Anda gunakan, seharusnya tidak ada banyak perbedaan. Dengan pencarian google cepat saya telah menemukan ini: stackoverflow.com/questions/3430194/… (Ini akan berlaku untuk Unity juga, meskipun bahasanya berbeda, itu masih logika dasar yang sama yang berlaku)
John Hamilton
2
Saya akan menambahkan: Dalam iterasi pertama Anda tidak memperbaiki kinerja tetapi prioritas harus arsitektur yang bersih dan gaya kode. Hanya optimalkan (kinerja) jika diperlukan nanti ...
Marco
2
Apakah Anda memiliki tautan ke ceramah itu?
Vaillancourt
2
Menurut halaman Unity ini , for eachloop berulang di atas apa pun selain array menghasilkan sampah (versi Unity sebelum 5.5)
Hellium
2
Saya melihat suara untuk menutup ini sebagai di luar topik karena ini adalah pemrograman umum, tetapi karena itu diminta dalam konteks Unity pada khususnya dan ini adalah perilaku yang telah berubah di antara versi Unity, saya pikir ada baiknya tetap membuka di sini untuk referensi Pengembang game unity.
DMGregory

Jawaban:

13

Loop foreach seolah-olah menciptakan objek IEnumerator dari koleksi yang Anda lewati, dan kemudian berjalan di atasnya. Jadi satu loop seperti ini:

foreach(var entry in collection)
{
    entry.doThing();
}

diterjemahkan menjadi sesuatu yang sedikit seperti ini:
(menghilangkan beberapa kerumitan tentang bagaimana enumerator dibuang nanti)

var enumerator = collection.GetEnumerator();
while(enumerator.MoveNext()) {
   var entry = enumerator.Current;
   entry.doThing();
}

Jika ini dikompilasi secara naif / langsung, maka objek enumerator itu akan dialokasikan pada heap (yang membutuhkan sedikit waktu), bahkan jika tipe dasarnya adalah struct - sesuatu yang disebut tinju. Setelah pengulangan selesai, alokasi ini menjadi sampah untuk dibersihkan nanti, dan permainan Anda dapat gagap sesaat jika cukup banyak sampah yang menumpuk bagi kolektor untuk menjalankan sapuan penuh. Terakhir, MoveNextmetode dan Currentproperti bisa melibatkan lebih banyak langkah-langkah pemanggilan fungsi daripada hanya mengambil item secara langsung collection[i], jika kompiler tidak sejalan dengan tindakan itu.

Unity docs tautan Hellium di atas menunjukkan bahwa bentuk naif ini adalah persis apa yang terjadi di Unity sebelum versi 5.5 , jika mengulangi apa pun selain dari Array asli.

Dalam versi 5.6+ (termasuk versi 2017), tinju yang tidak perlu ini hilang , dan Anda seharusnya tidak mengalami alokasi yang tidak perlu jika Anda menggunakan jenis beton apa pun (mis. int[]Atau List<GameObject>atau Queue<T>).

Anda masih akan mendapatkan alokasi jika metode yang dimaksud hanya tahu bahwa itu bekerja dengan semacam IList atau antarmuka lainnya - dalam kasus itu tidak tahu enumerator spesifik apa yang bekerja dengannya sehingga harus mengotakkannya menjadi objek IEnumerator terlebih dahulu .

Jadi, ada kemungkinan bagus ini saran lama yang tidak begitu penting dalam pengembangan Persatuan modern. Persatuan devs seperti kita bisa sedikit percaya pada hal-hal yang dulu lambat / berbulu dalam versi lama, jadi ikuti saran dari bentuk itu dengan sebutir garam. ;) Mesin berkembang lebih cepat daripada keyakinan kami tentang hal itu.

Dalam praktiknya, saya tidak pernah memiliki permainan yang menunjukkan kelambatan yang terlihat atau cegukan pengumpulan sampah karena menggunakan foreach loop, tetapi jarak tempuh Anda mungkin berbeda. Profil game Anda pada perangkat keras yang Anda pilih untuk melihat apakah itu layak untuk dikhawatirkan. Jika Anda perlu, itu cukup sepele untuk mengganti foreach loop dengan eksplisit untuk loop nanti dalam pengembangan harus manifes masalah, jadi saya tidak akan mengorbankan keterbacaan dan kemudahan pengkodean awal pengembangan.

DMGregory
sumber
Hanya untuk memeriksa, a List<MyCustomType>dan IEnumerator<MyCustomType> getListEnumerator() { }baik-baik saja, sedangkan metode IEnumerator getListEnumerator() { }tidak akan.
Draco18s tidak lagi mempercayai SE
0

Jika a untuk setiap loop digunakan untuk koleksi atau array objek (yaitu array semua elemen selain tipe data primitif), GC (Pengumpul Sampah) dipanggil untuk membebaskan ruang variabel referensi di akhir a untuk setiap loop.

foreach (Gameobject temp in Collection)
{
    //Code Do Task
}

Sedangkan untuk loop digunakan untuk beralih pada elemen menggunakan indeks dan tipe data primitif tidak akan mempengaruhi kinerja dibandingkan dengan tipe data non-primitif.

for (int i = 0; i < Collection.length; i++){
    //Get reference using index i;
    //Code Do Task
}
Tejas Jasani
sumber
Apakah Anda memiliki kutipan untuk ini? The tempvariabel di sini dapat dialokasikan pada stack, bahkan jika koleksi penuh referensi jenis (karena itu hanya referensi ke entri yang ada sudah di heap, tidak mengalokasikan memori heap baru untuk memegang sebuah contoh dari jenis referensi), sehingga kami tidak akan mengharapkan sampah terjadi di sini. Pencacah itu sendiri dapat dikotak dalam beberapa keadaan dan berakhir pada tumpukan, tetapi kasus-kasus tersebut berlaku untuk tipe data primitif juga.
DMGregory