Kes ingin tahu set ujian prestasiTimeout (0)

(Untuk kesan penuh, baca dengan suara serak sambil dikelilingi oleh awan asap)

Semuanya bermula pada hari musim gugur kelabu. Langit mendung, angin bertiup, dan seseorang memberitahu saya bahawa setTimeout(0)rata-rata menimbulkan kelewatan 4 ms. Mereka mendakwa itulah waktu yang diperlukan untuk melepaskan callback dari stack, ke antrian callback dan kembali ke stack lagi. Saya fikir ia terdengar mencurigakan (ini adalah sedikit yang anda bayangkan saya berwarna hitam dan putih dengan cerut di mulut saya). Memandangkan saluran paip rendering perlu dijalankan setiap 16 ms untuk membolehkan animasi lancar, 4 ms sepertinya lama untuk saya. Masa yang sangat lama.

Beberapa ujian naif di devtools dengan console.time()mengesahkannya. Kelewatan purata sepanjang 20 larian adalah kira-kira 1.5 ms. Sudah tentu, 20 larian tidak menghasilkan ukuran sampel yang mencukupi, tetapi sekarang saya ingin membuktikan. Saya ingin menjalankan ujian pada skala yang lebih besar yang dapat memberi saya jawapan yang lebih tepat. Saya tentu saja boleh pergi dan melambaikannya di wajah rakan sekerja saya untuk membuktikan bahawa mereka salah.

Mengapa kita melakukan apa yang kita buat?

Kaedah tradisional

Segera, saya mendapati diri saya berada di dalam air panas. Untuk mengukur berapa lama masa setTimeout(0)berjalan, saya memerlukan fungsi yang:

  • mengambil gambar masa sekarang
  • dilaksanakan setTimeout
  • kemudian keluar dengan segera sehingga timbunan akan jelas dan panggilan balik yang dijadualkan dapat berjalan dan mengira perbezaan waktu
  • dan saya memerlukan fungsi itu untuk menjalankan sebilangan besar kali sehingga pengiraannya bermakna secara statistik

Tetapi usaha membina untuk ini - untuk-gelung - tidak akan berjaya. Kerana for-loop tidak membersihkan timbunan sehingga ia melaksanakan setiap gelung, panggilan balik tidak akan berjalan dengan segera. Atau, untuk memasukkannya ke dalam kod, kami akan mendapatkannya:

Masalah di sini adalah wujud - jika saya ingin menjalankan setTimeoutberkali-kali secara automatik, saya harus melakukannya dari dalam konteks lain. Tetapi, selama saya berlari dari konteks lain, akan selalu ada kelewatan tambahan dari saat saya memulakan ujian hingga waktu panggilan balik dijalankan.

Sudah tentu saya dapat meneteskannya seperti beberapa detektif yang tidak berguna ini, menulis fungsi yang melakukan apa yang saya perlukan, dan kemudian menyalin & menampalnya 10,000 kali. Saya akan belajar apa yang ingin saya ketahui, tetapi pelaksanaannya akan jauh lebih baik. Sekiranya saya akan menggosok ini di wajah orang lain, saya lebih suka melakukannya dengan cara lain.

Kemudian datang kepada saya.

Kaedah revolusi

Saya boleh menggunakan pekerja web.

Pekerja laman web menggunakan rangkaian yang berbeza. Oleh itu, jika saya meletakkan setTimeoutlogik di pekerja web, saya dapat memanggilnya berkali-kali. Setiap panggilan akan membuat konteks pelaksanaannya sendiri, memanggil setTimeout, dan segera keluar dari fungsi sehingga panggilan balik dapat dilaksanakan. Saya tidak sabar untuk melakukan kerja dengan pekerja web.

Sudah tiba masanya untuk beralih ke Teks Sublime yang dipercayai saya.

Saya mula menguji perairan. Dengan kod ini di main.js:

Ada yang memasang paip di sini untuk persiapan ujian sebenar, tetapi pada mulanya saya hanya ingin memastikan saya dapat berkomunikasi dengan betul dengan pekerja web. Jadi ini adalah permulaan worker.js:

Dan walaupun ia berfungsi seperti daya tarikan - ia menghasilkan hasil yang semestinya saya harapkan, tetapi tidak:

Setelah terbiasa dengan sinkronisasi di JS, saya tidak dapat menahan diri daripada terkejut dengan perkara ini. Saat pertama saya melihat otak saya mendaftarkan bug. Tetapi, kerana setiap gelung menubuhkan pekerja web baru dan mereka berjalan secara tidak segerak, masuk akal bahawa nombor tersebut tidak akan dicetak mengikut urutan.

Mungkin mengejutkan saya, tetapi ia berfungsi seperti yang diharapkan. Saya boleh meneruskan ujian.

Apa yang saya mahukan adalah agar onmessagefungsi pekerja web mendaftar t0, memanggil setTimeout, dan kemudian segera keluar agar tidak menyekat tumpukan. Akan tetapi, saya dapat memasukkan fungsi tambahan di dalam panggilan balik, setelah saya menetapkan nilai t1. Saya menambahkan saya postMessageke panggilan balik, jadi ia tidak menyekat timbunan:

Dan inilah main.jskodnya:

Versi ini mempunyai masalah.

Sudah tentu - sejak saya baru bekerja di web, saya tidak menganggapnya pada mulanya. Tetapi, ketika beberapa fungsi berjalan terus dicetak 0, saya rasa ada yang tidak betul.

Semasa saya mencetak jumlah dari dalam, onmessagesaya mendapat jawapan saya. Fungsi utama bergerak secara serentak, dan tidak menunggu mesej dari pekerja untuk kembali, jadi ia mengira rata-rata sebelum pekerja web selesai.

Penyelesaian yang cepat dan kotor adalah dengan menambahkan pembilang dan melakukan pengiraan hanya apabila kaunter telah mencapai nilai maksimum. Jadi inilah yang barumain.js:

Dan inilah hasilnya:

main(10): 0.1

main(100) : 1.41

main(1000) : 13.082

Oh. Saya. Itu tidak bagus, bukan? Apa yang berlaku di sini?

Saya mengorbankan ujian prestasi untuk melihat ke dalam. Saya sekarang log t0dan t1 ketika mereka dibuat, hanya untuk melihat apa yang berlaku di sana.

Dan hasilnya:

Ternyata jangkaan saya untuk t1dikira dengan segera t0juga sesat. Pada hakikatnya, hakikat bahawa tiada apa-apa mengenai pekerja web adalah segerak bahawa andaian saya yang paling asas mengenai bagaimana kod saya berkelakuan tidak lagi berlaku. Ini adalah titik buta yang sukar dilihat.

Bukan hanya itu, malah hasil yang saya dapat main(10)dan main(100)yang pada awalnya membuat saya sangat gembira dan sombong, bukanlah sesuatu yang boleh saya percayai.

Asinkronisiti pekerja web juga menjadikan mereka proksi yang tidak boleh dipercayai untuk bagaimana sesuatu bertindak dalam timbunan biasa kami. Oleh itu, semasa mengukur prestasi setTimeoutpekerja web memberikan beberapa hasil yang menarik, ini bukan hasil yang menjawab persoalan kami.

Kaedah buku teks

Saya kecewa ... bolehkah saya tidak menemui penyelesaian JS vanila yang boleh menjadi elegan dan membuktikan rakan saya salah?

Dan kemudian saya sedar - ada sesuatu yang boleh saya lakukan, tetapi saya tidak menyukainya.

Saya boleh menghubungi setTimeoutsecara berulang.

Sekarang apabila saya memanggilnya, mainia akan memanggil testRunnermana yang mengukur t0dan kemudian menjadualkan panggilan balik. t1Panggilan balik kemudian dijalankan dengan serta-merta, mengira dan kemudian memanggil testRunnersemula, sehingga mencapai jumlah panggilan yang diinginkan.

Hasil kod ini sangat mengejutkan. Berikut adalah beberapa cetakan main(10)dan main(1000):

Hasilnya jauh berbeza ketika memanggil fungsi 1,000 kali berbanding memanggilnya 10 kali. Saya telah mencuba ini berulang kali dan mendapat hasil yang hampir sama, dengan main(10)masuk pada 3-4 ms, dan mencapai main(1000)puncak 5 ms.

Sejujurnya, saya tidak pasti apa yang berlaku di sini. Saya mencari jawapan, tetapi tidak menemui penjelasan yang munasabah. Sekiranya anda membaca ini dan mempunyai tekaan yang tepat tentang apa yang berlaku - saya ingin mendengar daripada anda dalam komen.

Kaedah yang dicuba dan benar

Di suatu tempat di dalam fikiran saya, saya selalu tahu bahawa perkara ini akan terjadi ... Perkara-perkara menarik sangat bagus bagi mereka yang dapat memperolehnya, tetapi dicuba dan benar akan selalu ada pada akhirnya. Walaupun saya cuba menghindarinya, saya selalu tahu ini adalah pilihan. setInterval.

Kod ini melakukan muslihat dengan kekuatan yang agak kasar. setIntervalmenjalankan fungsi berulang kali, menunggu 50 ms antara setiap larian, untuk memastikan timbunannya jelas. Ini tidak sesuai, tetapi menguji apa yang saya perlukan.

Dan hasilnya juga menjanjikan. Masa nampaknya sesuai dengan jangkaan asal saya - di bawah 1.5ms.

Akhirnya saya boleh meletakkan beg ini di tempat tidur. Saya mempunyai beberapa pasang surut, dan bahagian hasil saya yang tidak dijangka, tetapi pada akhirnya hanya satu perkara yang penting - saya telah membuktikan pembangun lain salah! Itu cukup baik untuk saya.

Ingin bermain-main dengan kod ini? lihat di sini: //github.com/NettaB/setTimeout-test