Ada satu pepatah: "Apa yang tidak bisa Anda ukur, Anda tidak bisa tingkatkan." Penulis artikel, terjemahan yang kami terbitkan hari ini, berfungsi untuk
Superhuman . Dia mengatakan perusahaan ini sedang mengembangkan klien email tercepat di dunia. Di sini kita akan berbicara tentang apa yang "cepat", dan bagaimana membuat alat untuk mengukur kinerja aplikasi web yang sangat cepat.

Pengukuran Kecepatan Aplikasi
Dalam upaya meningkatkan pengembangan kami, kami menghabiskan banyak waktu untuk mengukur kecepatannya. Dan, ternyata, metrik kinerja adalah indikator yang secara mengejutkan sulit dipahami dan diterapkan.
Di satu sisi, sulit untuk merancang metrik yang secara akurat menggambarkan sensasi yang dialami pengguna saat bekerja dengan sistem. Di sisi lain, tidak mudah untuk membuat metrik yang begitu akurat sehingga analisisnya memungkinkan Anda membuat keputusan yang tepat. Akibatnya, banyak tim pengembangan tidak dapat mempercayai data yang mereka kumpulkan pada kinerja proyek mereka.
Sekalipun pengembang memiliki metrik yang andal dan akurat, menggunakannya tidaklah mudah. Bagaimana cara mendefinisikan istilah "cepat"? Bagaimana menemukan keseimbangan antara kecepatan dan konsistensi? Bagaimana cara belajar dengan cepat mendeteksi penurunan kinerja atau belajar untuk mengevaluasi dampak optimisasi pada sistem?
Di sini kami ingin berbagi beberapa pemikiran mengenai pengembangan alat analisis kinerja aplikasi web.
1. Menggunakan "jam" yang tepat
JavaScript memiliki dua mekanisme untuk mengambil cap waktu:
performance.now()
dan
new Date()
.
Bagaimana mereka berbeda? Dua perbedaan berikut ini mendasar bagi kami:
- Metode
performance.now()
jauh lebih akurat. Keakuratan konstruksi new Date()
adalah ± 1 ms, sedangkan akurasi performance.now()
sudah ± 100 μs (ya, ini tentang mikrodetik !). - Nilai-nilai yang dikembalikan oleh metode
performance.now()
selalu meningkat pada tingkat yang konstan dan tidak tergantung pada waktu sistem. Metode ini hanya mengukur interval waktu tanpa berfokus pada waktu sistem. Dan pada new Date()
waktu sistem mempengaruhi. Jika Anda mengatur ulang jam sistem, itu juga akan mengubah new Date ()
apa yang kembali, dan ini akan merusak data pemantauan kinerja.
Meskipun "jam" yang diwakili oleh metode
performance.now()
jelas jauh lebih cocok untuk mengukur interval waktu, mereka juga tidak ideal. Baik
performance.now()
dan
new Date()
menderita dari masalah yang sama, yang memanifestasikan dirinya dalam hal sistem berada dalam kondisi tidur: pengukuran meliputi waktu ketika mesin bahkan tidak aktif.
2. Memeriksa aktivitas aplikasi
Jika Anda, mengukur kinerja aplikasi web, beralih dari tabnya ke yang lain - ini akan mengganggu proses pengumpulan data. Mengapa Faktanya adalah bahwa browser membatasi aplikasi yang terletak di tab latar belakang.
Ada dua situasi di mana metrik dapat terdistorsi. Akibatnya, aplikasi akan tampak jauh lebih lambat daripada yang sebenarnya.
- Komputer beralih ke mode tidur.
- Aplikasi berjalan di tab latar belakang browser.
Terjadinya kedua situasi ini tidak jarang terjadi. Untungnya, kami memiliki dua opsi untuk menyelesaikannya.
Pertama, kita bisa mengabaikan metrik yang terdistorsi, membuang hasil pengukuran yang terlalu berbeda dari beberapa nilai wajar. Misalnya, kode yang dipanggil ketika tombol ditekan tidak dapat dijalankan selama 15 menit! Mungkin ini adalah satu-satunya hal yang Anda butuhkan untuk menangani dua masalah yang dijelaskan di atas.
Kedua, Anda bisa menggunakan properti
document.hidden
dan acara
visibilitychange . Acara perubahan
visibilitychange
dinaikkan ketika pengguna beralih dari tab browser yang menarik ke tab lain atau kembali ke tab yang diinginkan kepada kami. Disebut ketika jendela browser meminimalkan atau memaksimalkan ketika komputer mulai bekerja, keluar dari mode tidur. Dengan kata lain, inilah tepatnya yang kita butuhkan. Selain itu, selama tab di latar belakang, properti
document.hidden
true
.
Berikut ini adalah contoh sederhana yang menunjukkan penggunaan properti
document.hidden
dan acara perubahan
visibilitychange
.
let lastVisibilityChange = 0 window.addEventListener('visibilitychange', () => { lastVisibilityChange = performance.now() }) // , , // , , if (metric.start < lastVisibilityChange || document.hidden) return
Seperti yang Anda lihat, kami membuang beberapa data, tetapi ini bagus. Faktanya adalah bahwa ini adalah data yang terkait dengan periode-periode program ketika tidak dapat sepenuhnya menggunakan sumber daya sistem.
Sekarang kita berbicara tentang indikator yang tidak menarik bagi kita. Tetapi ada banyak situasi, data yang dikumpulkan sangat menarik bagi kami. Mari kita lihat bagaimana cara mengumpulkan data ini.
3. Cari indikator yang memungkinkan Anda menangkap waktu acara dimulai
Salah satu fitur paling kontroversial dari JavaScript adalah loop acara untuk bahasa ini adalah utas tunggal. Pada titik waktu tertentu, hanya satu bagian kode yang dapat dieksekusi, eksekusi yang tidak dapat terganggu.
Jika pengguna menekan tombol saat mengeksekusi kode tertentu, program tidak akan mengetahuinya sampai eksekusi kode ini selesai. Misalnya, jika aplikasi menghabiskan 1000 ms dalam siklus berkelanjutan, dan pengguna menekan tombol
Escape
100 ms setelah dimulainya siklus, acara tidak akan direkam untuk 900 ms lainnya.
Ini dapat sangat mendistorsi metrik. Jika kita membutuhkan akurasi dalam mengukur dengan tepat bagaimana pengguna memandang bekerja dengan program, maka ini adalah masalah besar!
Untungnya, menyelesaikan masalah ini tidak begitu sulit. Jika kita berbicara tentang acara saat ini, maka kita dapat, alih-alih menggunakan
performance.now()
(waktu ketika kita melihat acara), gunakan
window.event.timeStamp
(waktu saat acara dibuat).
Stempel waktu acara diatur oleh proses browser utama. Karena proses ini tidak memblokir ketika loop peristiwa JS terkunci,
event.timeStamp
memberi kita informasi yang jauh lebih berharga tentang kapan acara itu benar-benar dipecat.
Perlu dicatat bahwa mekanisme ini tidak ideal. Jadi, di antara saat tombol fisik ditekan dan saat acara yang sesuai tiba di Chrome, 9-15 ms waktu yang tidak terhitung berlalu (di
sini adalah artikel yang bagus untuk Anda mengetahui mengapa ini terjadi).
Namun, meskipun kami dapat mengukur waktu yang dibutuhkan untuk acara untuk mencapai Chrome, kami tidak boleh memasukkan waktu ini dalam metrik kami. Mengapa Faktanya adalah bahwa kami tidak dapat memperkenalkan optimasi tersebut ke dalam kode yang dapat secara signifikan mempengaruhi keterlambatan tersebut. Kami tidak dapat memperbaikinya dengan cara apa pun.
Akibatnya, jika kita berbicara tentang menemukan cap waktu untuk memulai acara, maka indikator
event.timeStamp
terlihat paling memadai di sini.
Apa estimasi terbaik saat acara berakhir?
4. Matikan timer di requestAnimationFrame ()
Satu konsekuensi lagi mengikuti dari fitur-fitur perangkat loop acara di JavaScript: beberapa kode yang tidak terkait dengan kode Anda dapat dieksekusi setelah itu, tetapi sebelum browser menampilkan versi halaman yang diperbarui pada layar.
Pertimbangkan, misalnya, Bereaksi. Setelah menjalankan kode Anda, React memperbarui DOM. Jika Anda hanya mengukur waktu dalam kode Anda, itu berarti Anda tidak akan mengukur waktu yang diperlukan untuk menjalankan kode Bereaksi.
Untuk mengukur waktu tambahan ini, kami menggunakan
requestAnimationFrame()
untuk mematikan timer. Ini dilakukan hanya ketika browser siap untuk menampilkan frame berikutnya.
requestAnimationFrame(() => { metric.finish(performance.now()) })
Berikut ini adalah siklus hidup frame (diagram diambil dari materi yang luar biasa
ini berdasarkan
requestAnimationFrame
).
Bingkai siklus hidupSeperti yang Anda lihat pada gambar ini,
requestAnimationFrame()
dipanggil setelah prosesor selesai, tepat sebelum bingkai ditampilkan. Jika kita mematikan timer di sini, itu berarti kita dapat benar-benar yakin bahwa semua yang membutuhkan waktu untuk menyegarkan layar termasuk dalam data yang dikumpulkan pada interval waktu.
Sejauh ini bagus, tapi sekarang situasinya menjadi agak rumit ...
5. Mengabaikan waktu yang diperlukan untuk membuat tata letak halaman dan visualisasinya.
Diagram sebelumnya, menunjukkan siklus hidup suatu bingkai, menggambarkan masalah lain yang kami temui. Di akhir siklus hidup frame, ada blok Layout (membentuk tata letak halaman) dan Paint (menampilkan halaman). Jika Anda tidak memperhitungkan waktu yang diperlukan untuk menyelesaikan operasi ini, maka waktu yang diukur oleh kami akan kurang dari waktu yang diperlukan untuk beberapa data yang diperbarui muncul di layar.
Untungnya,
requestAnimationFrame
memiliki kartu as lainnya. Ketika fungsi yang dilewati oleh
requestAnimationFrame
dipanggil, fungsi ini melewati timestamp yang menunjukkan waktu mulai dari pembentukan frame saat ini (yaitu, yang terletak di bagian paling kiri dari diagram kita). Stempel waktu ini biasanya sangat dekat dengan waktu akhir bingkai sebelumnya.
Akibatnya, kelemahan di atas dapat diperbaiki dengan mengukur total waktu yang telah berlalu sejak saat
event.timeStamp
ke waktu ketika pembentukan frame berikutnya dimulai. Perhatikan permintaan bersarangAnimationFrame:
requestAnimationFrame(() => { requestAnimationFrame((timestamp) => { metric.finish(timestamp) }) })
Meskipun apa yang ditunjukkan di atas terlihat seperti solusi yang sangat baik untuk masalah ini, pada akhirnya, kami memutuskan untuk tidak menggunakan desain ini. Faktanya adalah bahwa, meskipun teknik ini memungkinkan untuk memperoleh data yang lebih andal, keakuratan data tersebut berkurang. Frame di Chrome dibentuk dengan frekuensi 16 ms. Ini berarti bahwa akurasi tertinggi yang tersedia bagi kami adalah ± 16 ms. Dan jika browser kelebihan beban dan melompati bingkai, maka akurasinya akan lebih rendah, dan kemunduran ini tidak dapat diprediksi.
Jika Anda menerapkan solusi ini, maka peningkatan serius dalam kinerja kode Anda, seperti mempercepat tugas yang sebelumnya dilakukan 32 ms, hingga 15 ms, mungkin tidak mempengaruhi hasil pengukuran kinerja.
Tidak memperhitungkan waktu yang diperlukan untuk membuat tata letak halaman dan hasilnya, kami mendapatkan metrik yang jauh lebih akurat (± 100 μs) untuk kode yang berada di bawah kendali kami. Sebagai hasilnya, kita bisa mendapatkan ekspresi numerik dari setiap perbaikan yang dilakukan pada kode ini.
Kami juga mengeksplorasi ide serupa:
requestAnimationFrame(() => { setTimeout(() => { metric.finish(performance.now()) } })
Ini akan termasuk waktu render, tetapi keakuratan indikator tidak akan dibatasi hingga ± 16 ms. Namun, kami memutuskan untuk tidak menggunakan pendekatan ini juga. Jika sistem menemukan event input yang panjang, maka panggilan ke
setTimeout
ditransmisikan dapat secara signifikan ditunda dan dieksekusi setelah antarmuka pengguna diperbarui.
6. Klarifikasi "persentase acara yang di bawah target"
Kami sedang mengembangkan proyek dan berfokus pada kinerja tinggi, mencoba mengoptimalkannya dengan dua cara:
- Kecepatan. Waktu pelaksanaan tugas tercepat harus sedekat mungkin dengan 0 ms.
- Keseragaman. Waktu pelaksanaan tugas yang paling lambat harus sedekat mungkin dengan waktu pelaksanaan tugas tercepat.
Karena fakta bahwa indikator-indikator ini berubah dari waktu ke waktu, mereka sulit untuk divisualisasikan dan tidak mudah untuk didiskusikan. Apakah mungkin untuk membuat sistem visualisasi indikator yang akan menginspirasi kita untuk mengoptimalkan kecepatan dan keseragaman?
Pendekatan tipikal adalah mengukur persentil ke-90 dari keterlambatan. Pendekatan ini memungkinkan Anda untuk menggambar grafik garis di sepanjang sumbu Y yang waktu dalam milidetik disimpan. Grafik ini memungkinkan Anda untuk melihat bahwa 90% dari peristiwa di bawah grafik garis, yaitu, mereka mengeksekusi lebih cepat daripada waktu yang ditunjukkan grafik garis.
Diketahui bahwa
100 ms adalah batas antara apa yang dianggap sebagai "cepat" dan "lambat".
Tapi apa yang akan kita temukan tentang bagaimana perasaan pengguna dari pekerjaan jika kita tahu bahwa persentil ke-90 keterlambatan adalah 103 ms? Tidak terlalu banyak. Indikator apa yang akan memberi pengguna kegunaan? Tidak ada cara untuk mengetahui hal ini dengan pasti.
Tetapi bagaimana jika kita tahu bahwa persentil ke-90 dari penundaan adalah 93 ms? Ada perasaan bahwa 93 lebih baik daripada 103, tetapi kami tidak bisa mengatakan apa-apa lagi tentang indikator-indikator ini, serta apa artinya dalam hal persepsi pengguna terhadap proyek. Sekali lagi, tidak ada jawaban pasti untuk pertanyaan ini.
Kami telah menemukan solusi untuk masalah ini. Ini terdiri dalam mengukur persentase peristiwa yang waktu eksekusi tidak melebihi 100 ms. Ada tiga keuntungan besar untuk pendekatan ini:
- Metrik ini berorientasi pada pengguna. Dia dapat memberi tahu kami berapa persen waktu aplikasi kami cepat, dan berapa persen pengguna menganggapnya cepat.
- Metrik ini memungkinkan kami mengembalikan pengukuran ke akurasi yang hilang karena fakta bahwa kami tidak mengukur waktu yang diperlukan untuk menyelesaikan tugas di bagian paling akhir frame (kami membicarakannya di bagian No. 5). Karena kami menetapkan indikator target yang sesuai dengan beberapa bingkai, hasil pengukuran yang dekat dengan indikator ini ternyata lebih kecil atau lebih banyak.
- Metrik ini lebih mudah untuk dihitung. Cukup menghitung jumlah peristiwa yang waktu pelaksanaannya di bawah indikator target, dan setelah itu - bagi dengan jumlah total peristiwa. Persentil jauh lebih sulit untuk dihitung. Ada perkiraan yang efektif, tetapi untuk melakukan semuanya dengan benar, Anda harus memperhitungkan setiap dimensi.
Pendekatan ini hanya memiliki satu minus: jika indikator lebih buruk daripada target, maka tidak akan mudah untuk melihat peningkatannya.
7. Penggunaan beberapa nilai ambang batas dalam analisis indikator
Untuk memvisualisasikan hasil optimasi kinerja, kami memperkenalkan beberapa nilai ambang tambahan ke sistem kami - di atas 100 ms dan di bawah.
Kami mengelompokkan penundaan seperti ini:
- Kurang dari 50 ms (cepat).
- 50 hingga 100 ms (bagus).
- 100 hingga 1000 ms (lambat).
- Lebih dari 1000 ms (sangat lambat).
Hasil "Sangat lambat" memungkinkan kita untuk melihat bahwa kita telah sangat merindukan suatu tempat. Karena itu, kami menyorotinya dengan warna merah cerah.
Apa yang cocok dalam 50 ms sangat sensitif terhadap perubahan. Di sini, peningkatan kinerja sering terlihat jauh sebelum terlihat dalam grup yang sesuai dengan 100 ms.
Sebagai contoh, grafik berikut memvisualisasikan kinerja melihat thread di Superhuman.
Lihat utasIni menunjukkan periode penurunan kinerja, dan kemudian - hasil perbaikan. Sulit untuk menilai penurunan kinerja jika Anda hanya melihat indikator yang sesuai dengan 100 ms (bagian atas kolom biru). Ketika melihat hasil yang sesuai dengan 50 ms (bagian atas kolom hijau), masalah kinerja sudah terlihat jauh lebih jelas.
Jika kami menggunakan pendekatan tradisional untuk mempelajari metrik kinerja, kami mungkin tidak akan melihat masalah yang pengaruhnya terhadap sistem ditunjukkan pada gambar sebelumnya. Namun berkat cara kami melakukan pengukuran dan cara memvisualisasikan metrik kami, kami dapat dengan cepat menemukan dan memecahkan masalah.
Ringkasan
Ternyata sulit untuk menemukan pendekatan yang tepat untuk bekerja dengan metrik kinerja. Kami berhasil mengembangkan metodologi yang memungkinkan kami membuat alat berkualitas tinggi untuk mengukur kinerja aplikasi web. Yaitu, kita berbicara tentang hal berikut:
- Waktu mulai dari suatu peristiwa diukur menggunakan
event.timeStamp
. - Waktu akhir acara diukur menggunakan
performance.now()
di dalam callback yang diteruskan ke requestAnimationFrame()
. - Segala sesuatu yang terjadi dengan aplikasi saat berada di tab browser yang tidak aktif diabaikan.
- Data dikumpulkan menggunakan indikator, yang dapat digambarkan sebagai "persentase kejadian yang di bawah target".
- Data divisualisasikan dengan beberapa tingkatan nilai ambang batas.
Teknik ini memberi Anda alat untuk membuat metrik yang andal dan akurat. Anda dapat membuat grafik yang dengan jelas menunjukkan penurunan kinerja, Anda dapat memvisualisasikan hasil optimasi. Dan yang paling penting - Anda memiliki kesempatan untuk membuat proyek cepat lebih cepat.
Pembaca yang budiman! Bagaimana Anda menganalisis kinerja aplikasi web Anda?
