Rumah >hujung hadapan web >tutorial js >Loop Unrolling dalam JavaScript?
JavaScript boleh berasa sangat terkeluar daripada perkakasan yang dijalankan, tetapi memikirkan tahap rendah masih boleh berguna dalam kes terhad.
Siaran terbaru Kafeel Ahmad mengenai pengoptimuman gelung memperincikan beberapa teknik peningkatan prestasi gelung. Artikel itu membuatkan saya berfikir tentang topik itu.
Hanya untuk menyelesaikan masalah ini, ini adalah teknik yang sangat sedikit yang perlu dipertimbangkan dalam pembangunan web. Selain itu, memfokuskan pada pengoptimuman terlalu awal boleh menjadikan kod lebih sukar untuk ditulis dan lebih sukar diselenggara. Mengintip teknik peringkat rendah boleh memberi kita cerapan tentang alatan kami dan kerja secara umum, walaupun kami tidak dapat menggunakan pengetahuan itu secara langsung.
Pembukaan gelung pada asasnya menduplikasi logik di dalam gelung supaya anda melakukan berbilang operasi semasa setiap satu, baik, gelung. Dalam kes tertentu, menjadikan kod dalam gelung lebih panjang boleh menjadikannya lebih pantas.
Dengan sengaja melakukan beberapa operasi dalam kumpulan dan bukannya satu demi satu, komputer mungkin dapat beroperasi dengan lebih cekap.
Mari kita ambil contoh yang sangat mudah: menjumlahkan nilai dalam tatasusunan.
// 1-to-1 looping const simpleSum = (data) => { let sum = 0; for(let i=0; i < data.length; i += 1) { sum += data[i]; } return sum; }; const parallelSum = (data) => { let sum1 = 0; let sum2 = 0; for(let i=0; i < data.length; i += 2) { sum1 += data[i]; sum2 += data[i + 1]; } return sum1 + sum2; };
Ini mungkin kelihatan sangat pelik pada mulanya. Kami menguruskan lebih banyak pembolehubah dan melaksanakan operasi tambahan yang tidak berlaku dalam contoh mudah. Bagaimana ini boleh menjadi lebih pantas?!
Saya menjalankan beberapa perbandingan ke atas pelbagai saiz data dan berbilang larian, serta ujian berjujukan atau bersilang. Prestasi parallelSum berbeza-beza, tetapi hampir selalu lebih baik, kecuali beberapa keputusan ganjil untuk saiz data yang sangat kecil. Saya menguji ini menggunakan RunJS, yang dibina pada enjin V8 Chrome.
Saiz data yang berbeza memberikan secara kasar hasil ini:
Kemudian saya mencipta JSPerf dengan 1 juta rekod untuk mencuba merentasi pelayar yang berbeza. Cubalah sendiri!
Chrome menjalankan parallelSum dua kali lebih pantas daripada simpleSum, seperti yang dijangkakan daripada ujian RunJS.
Safari hampir sama dengan Chrome, dalam peratusan dan operasi sesaat.
Firefox pada sistem yang sama menunjukkan prestasi yang hampir sama untuk simpleSum tetapi parallelSum hanya kira-kira 15% lebih pantas, bukan dua kali lebih pantas.
Variasi ini menghantar saya mencari maklumat lanjut. Walaupun ia bukan sesuatu yang pasti, saya menemui komen StackOverflow dari 2016 membincangkan beberapa isu enjin JS dengan pembukaan gelung. Ini adalah pandangan yang menarik tentang cara enjin dan pengoptimuman boleh mempengaruhi kod dalam cara yang tidak kami jangkakan.
Saya juga mencuba versi ketiga, yang menambahkan dua nilai dalam satu operasi untuk melihat sama ada terdapat perbezaan ketara antara satu pembolehubah dan dua.
const parallelSum = (data) => { let sum = 0 for(let i=0; i < data.length; i += 2) { sum += data[i] + data[i + 1]; } return sum; };
Jawapan ringkas: Tidak. Kedua-dua versi "selari" berada dalam margin ralat yang dilaporkan antara satu sama lain.
Walaupun JavaScript berbenang tunggal, penterjemah, penyusun dan perkakasan di bawahnya boleh melakukan pengoptimuman untuk kami apabila syarat tertentu dipenuhi.
Dalam contoh mudah, operasi memerlukan nilai i untuk mengetahui data yang hendak diambil dan ia memerlukan nilai jumlah terkini untuk dikemas kini. Oleh kerana kedua-dua perubahan ini dalam setiap gelung, komputer perlu menunggu sehingga gelung selesai untuk mendapatkan lebih banyak data. Walaupun nampak jelas kepada kita perkara yang akan dilakukan oleh i += 1, komputer kebanyakannya memahami "nilai akan berubah, semak semula kemudian", jadi ia mengalami kesukaran untuk mengoptimumkan.
Versi selari kami memuatkan berbilang entri data untuk setiap nilai i. Kami masih bergantung pada jumlah untuk setiap gelung, tetapi kami boleh memuatkan dan memproses dua kali lebih banyak data bagi setiap kitaran. Tetapi itu tidak bermakna ia berjalan dua kali lebih pantas.
Untuk memahami sebab pembukaan gelung berfungsi, kami melihat kepada pengendalian peringkat rendah komputer. Pemproses dengan seni bina super-skalar boleh mempunyai berbilang saluran paip untuk melaksanakan operasi serentak. Mereka boleh menyokong pelaksanaan luar perintah supaya operasi yang tidak bergantung antara satu sama lain boleh berlaku secepat mungkin. Untuk sesetengah operasi, SIMD boleh melakukan satu tindakan pada berbilang keping data sekaligus. Selain itu, kami mula memasuki caching, pengambilan data dan ramalan cawangan...
Tetapi ini adalah artikel JavaScript! Kami tidak akan sedalam itu. Jika anda ingin mengetahui lebih lanjut tentang seni bina pemproses, Anandtech mempunyai Deep Dives yang sangat baik.
Membuka gelung bukan sihir. Terdapat had dan pulangan yang semakin berkurangan yang muncul kerana saiz program atau data, kerumitan operasi, seni bina komputer dan banyak lagi. Tetapi kami hanya menguji satu atau dua operasi dan komputer moden selalunya menyokong empat atau lebih urutan.
Untuk mencuba beberapa kenaikan yang lebih besar, saya membuat JSPerf lain dengan 1, 2, 4, dan 10 rekod dan menjalankannya pada Apple M1 Max MacBook Pro yang menjalankan macOS 14.5 Sonoma dan PC AMD Ryzen 9 3950X yang menjalankan Windows 11.
Sepuluh rekod pada satu masa adalah 2.5-3.5x lebih pantas daripada gelung asas, tetapi hanya 12-15% lebih pantas daripada memproses empat rekod pada Mac. Pada PC kami masih melihat peningkatan 2x ganda antara satu hingga dua rekod, tetapi sepuluh rekod hanya 2% lebih pantas daripada empat rekod, yang saya tidak akan ramalkan untuk pemproses 16 teras.
Hasil yang berbeza ini mengingatkan kita untuk berhati-hati dengan pengoptimuman. Mengoptimumkan komputer anda boleh mencipta pengalaman yang lebih buruk pada perkakasan yang kurang berkemampuan atau hanya berbeza. Isu prestasi atau kefungsian untuk perkakasan yang lebih lama atau peringkat permulaan ialah isu biasa apabila pembangun mengusahakan mesin yang pantas dan berkuasa dan ia adalah sesuatu yang telah saya tugaskan beberapa kali dalam kerjaya saya.
Untuk beberapa skala prestasi, Chromebook peringkat permulaan yang tersedia pada masa ini daripada HP mempunyai pemproses Intel Celeron N4120. Ini kira-kira bersamaan dengan 2013 Core i5-4250U MacBook Air saya. Ia hanya mempunyai satu kesembilan prestasi M1 Max dalam penanda aras sintetik. Pada MacBook Air 2013 itu, menjalankan versi terbaharu Chrome, fungsi 4 rekod adalah lebih pantas daripada 10 rekod, tetapi masih hanya 60% lebih pantas daripada fungsi rekod tunggal!
Pelayar dan piawaian juga sentiasa berubah. Kemas kini penyemak imbas rutin atau seni bina pemproses yang berbeza boleh menjadikan kod yang dioptimumkan lebih perlahan daripada gelung biasa. Apabila anda mendapati diri anda mengoptimumkan secara mendalam, anda mungkin perlu memastikan pengoptimuman anda adalah relevan kepada pengguna anda dan bahawa ia kekal relevan.
Ia mengingatkan saya kepada buku JavaScript Prestasi Tinggi oleh Nicholas Zakas, yang saya baca pada tahun 2012. Ia adalah buku yang hebat dan mengandungi banyak cerapan. Walau bagaimanapun, menjelang 2014 beberapa isu prestasi penting yang dikenal pasti dalam buku itu telah diselesaikan atau dikurangkan dengan ketara oleh kemas kini enjin penyemak imbas dan kami dapat menumpukan lebih banyak usaha untuk menulis kod yang boleh diselenggara.
Jika anda cuba kekal pada kelebihan pengoptimuman prestasi, bersedia untuk perubahan dan pengesahan tetap.
Semasa meneliti topik ini, saya menjumpai utas Linux Kernel Mailing List dari tahun 2000 tentang mengalih keluar beberapa pengoptimuman pembukaan gelung yang akhirnya meningkatkan prestasi aplikasi. Ia termasuk perkara yang masih relevan ini (penekanan saya):
Intinya ialah andaian intuitif kami tentang apa yang pantas dan apa yang tidak boleh selalunya salah, terutamanya memandangkan berapa banyak CPU telah berubah sejak beberapa tahun lalu.
– Theodore Ts'o
Ada kalanya anda mungkin perlu memerah prestasi daripada satu gelung, dan jika anda memproses item yang mencukupi, ini boleh menjadi salah satu cara anda melakukannya. Adalah baik untuk mengetahui tentang pengoptimuman jenis ini, tetapi untuk kebanyakan kerja, You Aren't Gonna Need It™.
Namun saya harap anda telah menikmati cerita saya dan mungkin pada masa hadapan ingatan anda akan digerakkan tentang pertimbangan pengoptimuman prestasi.
Terima kasih kerana membaca!
Atas ialah kandungan terperinci Loop Unrolling dalam JavaScript?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!