Terlepas dari jenis aplikasi yang Anda kembangkan, mengoptimalkan performanya dan memastikan aplikasi dimuat dengan cepat serta menawarkan interaksi yang lancar sangat penting untuk pengalaman pengguna dan keberhasilan aplikasi. Salah satu cara untuk melakukannya adalah dengan memeriksa aktivitas aplikasi menggunakan alat pembuatan profil untuk melihat apa yang terjadi di balik layarnya saat berjalan selama jangka waktu tertentu. Panel Performa di DevTools adalah alat pembuatan profil yang sangat baik untuk menganalisis dan mengoptimalkan performa aplikasi web. Jika aplikasi Anda berjalan di Chrome, Anda akan mendapatkan ringkasan visual mendetail tentang aktivitas browser saat aplikasi Anda dieksekusi. Memahami aktivitas ini dapat membantu Anda mengidentifikasi pola, hambatan, dan titik aktif performa yang dapat Anda tindak lanjuti untuk meningkatkan performa.
Contoh berikut akan memandu Anda menggunakan panel Performa.
Menyiapkan dan membuat ulang skenario pembuatan profil
Baru-baru ini, kami menetapkan sasaran untuk membuat panel Performa berperforma lebih baik. Khususnya, kami ingin memuat volume data performa yang besar dengan lebih cepat. Hal ini terjadi, misalnya, saat memprofilkan proses yang berjalan lama atau kompleks atau mengambil data dengan perincian tinggi. Untuk mencapai hal ini, pemahaman tentang cara aplikasi berjalan dan alasan aplikasi berjalan seperti itu diperlukan terlebih dahulu, yang dicapai dengan menggunakan alat pembuatan profil.
Seperti yang mungkin Anda ketahui, DevTools sendiri adalah aplikasi web. Oleh karena itu, performanya dapat diprofilkan menggunakan panel Performance. Untuk memprofilkan panel ini sendiri, Anda dapat membuka DevTools, lalu membuka instance DevTools lain yang terlampir padanya. Di Google, penyiapan ini dikenal sebagai DevTools-on-DevTools.
Setelah penyiapan siap, skenario yang akan diprofilkan harus dibuat ulang dan direkam. Untuk menghindari kebingungan, jendela DevTools asli akan disebut sebagai "instance DevTools pertama", dan jendela yang memeriksa instance pertama akan disebut sebagai "instance DevTools kedua".

Pada instance DevTools kedua, panel Performance—yang selanjutnya akan disebut panel perf—mengamati instance DevTools pertama untuk membuat ulang skenario, yang memuat profil.
Di instance DevTools kedua, rekaman live dimulai, sementara di instance pertama, profil dimuat dari file di disk. File berukuran besar dimuat untuk memprofilkan performa pemrosesan input berukuran besar secara akurat. Saat kedua instance selesai dimuat, data pembuatan profil performa—yang biasa disebut rekaman aktivitas—akan terlihat di instance DevTools kedua dari panel perf yang memuat profil.
Kondisi awal: mengidentifikasi peluang peningkatan
Setelah pemuatan selesai, hal berikut pada instance panel performa kedua kami diamati pada screenshot berikutnya. Fokus pada aktivitas thread utama, yang terlihat di bagian jalur berlabel Main. Dapat dilihat bahwa ada lima grup besar aktivitas dalam flame chart. Bagian ini terdiri dari tugas yang pemuatannya membutuhkan waktu paling lama. Total waktu untuk menyelesaikan tugas ini adalah sekitar 10 detik. Pada screenshot berikut, panel performa digunakan untuk berfokus pada setiap grup aktivitas ini untuk melihat apa yang dapat ditemukan.

Grup aktivitas pertama: pekerjaan yang tidak perlu
Ternyata grup aktivitas pertama adalah kode lama yang masih berjalan, tetapi sebenarnya tidak diperlukan. Pada dasarnya, semua yang ada di bawah blok hijau berlabel processThreadEvents
adalah upaya yang sia-sia. Yang itu adalah solusi cepat. Menghapus panggilan fungsi tersebut menghemat waktu sekitar 1,5 detik. Keren!
Grup aktivitas kedua
Dalam grup aktivitas kedua, solusinya tidak sesederhana grup pertama. buildProfileCalls
membutuhkan waktu sekitar 0,5 detik, dan tugas tersebut tidak dapat dihindari.

Karena penasaran, kami mengaktifkan opsi Memori di panel performa untuk menyelidiki lebih lanjut, dan melihat bahwa aktivitas buildProfileCalls
juga menggunakan banyak memori. Di sini, Anda dapat melihat bagaimana grafik garis biru tiba-tiba melonjak di sekitar waktu buildProfileCalls
dijalankan, yang menunjukkan potensi kebocoran memori.

Untuk menindaklanjuti kecurigaan ini, kami menggunakan panel Memori (panel lain di DevTools, berbeda dengan panel Memori di panel performa) untuk menyelidiki. Dalam panel Memory, jenis pembuatan profil "Allocation sampling" dipilih, yang merekam snapshot heap untuk panel perf yang memuat profil CPU.

Screenshot berikut menunjukkan snapshot heap yang dikumpulkan.

Dari snapshot heap ini, diamati bahwa class Set
menggunakan banyak memori. Dengan memeriksa titik panggilan, ditemukan bahwa kita tidak perlu menetapkan properti jenis Set
ke objek yang dibuat dalam volume besar. Biaya ini terus bertambah dan banyak memori yang digunakan, hingga aplikasi sering mengalami error pada input yang besar.
Set berguna untuk menyimpan item unik dan menyediakan operasi yang menggunakan keunikan kontennya, seperti menghapus duplikat set data dan menyediakan pencarian yang lebih efisien. Namun, fitur tersebut tidak diperlukan karena data yang disimpan dijamin unik dari sumbernya. Oleh karena itu, set tidak diperlukan sejak awal. Untuk meningkatkan alokasi memori, jenis properti diubah dari Set
menjadi array biasa. Setelah menerapkan perubahan ini, snapshot heap lain diambil, dan alokasi memori yang lebih kecil diamati. Meskipun tidak mencapai peningkatan kecepatan yang signifikan dengan perubahan ini, manfaat sekundernya adalah aplikasi menjadi lebih jarang mengalami error.

Grup aktivitas ketiga: mempertimbangkan kompromi struktur data
Bagian ketiga aneh: Anda dapat melihat dalam flame chart bahwa bagian ini terdiri dari kolom sempit tetapi tinggi, yang menunjukkan panggilan fungsi yang dalam, dan rekursi yang dalam dalam kasus ini. Secara keseluruhan, bagian ini berlangsung sekitar 1,4 detik. Dengan melihat bagian bawah bagian ini, terlihat jelas bahwa lebar kolom ini ditentukan oleh durasi satu fungsi: appendEventAtLevel
, yang menunjukkan bahwa fungsi tersebut dapat menjadi hambatan
Di dalam implementasi fungsi appendEventAtLevel
, ada satu hal yang menarik. Untuk setiap entri data tunggal dalam input (yang dikenal dalam kode sebagai "peristiwa"), item ditambahkan ke peta yang melacak posisi vertikal entri linimasa. Hal ini menimbulkan masalah karena jumlah item yang disimpan sangat besar. Peta cepat untuk pencarian berbasis kunci, tetapi keunggulan ini tidak gratis. Seiring bertambah besarnya peta, penambahan data ke peta tersebut dapat menjadi mahal, misalnya karena melakukan penghasahan ulang. Biaya ini akan terasa saat sejumlah besar item ditambahkan ke peta secara berurutan.
/**
* Adds an event to the flame chart data at a defined vertical level.
*/
function appendEventAtLevel (event, level) {
// ...
const index = data.length;
data.push(event);
this.indexForEventMap.set(event, index);
// ...
}
Kami bereksperimen dengan pendekatan lain yang tidak mengharuskan kami menambahkan item di peta untuk setiap entri dalam flame chart. Peningkatannya signifikan, yang mengonfirmasi bahwa hambatan memang terkait dengan overhead yang terjadi akibat menambahkan semua data ke peta. Waktu yang dibutuhkan grup aktivitas berkurang dari sekitar 1,4 detik menjadi sekitar 200 milidetik.
Sebelum:

Setelah:

Grup aktivitas keempat: menunda pekerjaan yang tidak penting dan menyimpan data cache untuk mencegah pekerjaan duplikat
Dengan memperbesar jendela ini, dapat dilihat bahwa ada dua blok panggilan fungsi yang hampir identik. Dengan melihat nama fungsi yang dipanggil, Anda dapat menyimpulkan bahwa blok ini terdiri dari kode yang membangun hierarki (misalnya, dengan nama seperti refreshTree
atau buildChildren
). Faktanya, kode terkait adalah kode yang membuat tampilan hierarki di panel laci bawah. Yang menarik adalah tampilan hierarki ini tidak ditampilkan tepat setelah pemuatan. Sebagai gantinya, pengguna perlu memilih tampilan hierarki (tab "Bottom-up", "Call Tree", dan "Event Log" di panel) agar hierarki ditampilkan. Selain itu, seperti yang dapat Anda lihat dari screenshot, proses pembuatan hierarki dijalankan dua kali.

Ada dua masalah yang kami identifikasi pada gambar ini:
- Tugas yang tidak penting menghambat performa waktu pemuatan. Pengguna tidak selalu memerlukan outputnya. Dengan demikian, tugas ini tidak penting untuk pemuatan profil.
- Hasil tugas ini tidak di-cache. Itulah sebabnya pohon dihitung dua kali, meskipun datanya tidak berubah.
Kami memulai dengan menunda penghitungan hierarki saat pengguna membuka tampilan hierarki secara manual. Baru kemudian kita akan merasa bahwa harga yang dibayarkan untuk menanam pohon ini sepadan. Total waktu untuk menjalankan ini dua kali adalah sekitar 3,4 detik, jadi menundanya membuat perbedaan yang signifikan dalam waktu pemuatan. Kami juga masih menyelidiki cara menyimpan tugas jenis ini dalam cache.
Grup aktivitas kelima: hindari hierarki panggilan yang rumit jika memungkinkan
Dengan melihat grup ini secara cermat, jelas bahwa rantai panggilan tertentu dipanggil berulang kali. Pola yang sama muncul 6 kali di berbagai tempat dalam diagram flame, dan total durasi jendela ini sekitar 2,4 detik.

Kode terkait yang dipanggil beberapa kali adalah bagian yang memproses data untuk dirender di "peta mini" (ringkasan aktivitas linimasa di bagian atas panel). Tidak jelas mengapa hal itu terjadi beberapa kali, tetapi yang pasti hal itu tidak perlu terjadi 6 kali. Faktanya, output kode akan tetap terbaru jika tidak ada profil lain yang dimuat. Secara teori, kode hanya boleh dijalankan satu kali.
Setelah diselidiki, ditemukan bahwa kode terkait dipanggil sebagai akibat dari beberapa bagian dalam pipeline pemuatan yang secara langsung atau tidak langsung memanggil fungsi yang menghitung peta mini. Hal ini karena kompleksitas grafik panggilan program berkembang dari waktu ke waktu, dan lebih banyak dependensi pada kode ini ditambahkan tanpa disadari. Tidak ada perbaikan cepat untuk masalah ini. Cara mengatasinya bergantung pada arsitektur codebase yang dimaksud. Dalam kasus ini, kami harus mengurangi sedikit kompleksitas hierarki panggilan dan menambahkan pemeriksaan untuk mencegah eksekusi kode jika data input tidak berubah. Setelah menerapkan ini, kami mendapatkan tampilan linimasa berikut:

Perhatikan bahwa eksekusi rendering peta mini terjadi dua kali, bukan sekali. Hal ini karena ada dua peta mini yang digambar untuk setiap profil: satu untuk ringkasan di bagian atas panel, dan satu lagi untuk menu drop-down yang memilih profil yang saat ini terlihat dari histori (setiap item dalam menu ini berisi ringkasan profil yang dipilihnya). Namun demikian, kedua file ini memiliki konten yang sama persis, sehingga salah satunya dapat digunakan kembali untuk file lainnya.
Karena peta mini ini adalah gambar yang digambar di kanvas, kita hanya perlu menggunakan drawImage
utilitas kanvas, lalu menjalankan kode hanya sekali untuk menghemat waktu. Sebagai hasil dari upaya ini, durasi grup dikurangi dari 2,4 detik menjadi 140 milidetik.
Kesimpulan
Setelah menerapkan semua perbaikan ini (dan beberapa perbaikan kecil lainnya di sana-sini), perubahan pada linimasa pemuatan profil terlihat sebagai berikut:
Sebelum:

Setelah:

Waktu pemuatan setelah peningkatan adalah 2 detik, yang berarti peningkatan sekitar 80% dicapai dengan upaya yang relatif rendah, karena sebagian besar yang dilakukan terdiri dari perbaikan cepat. Tentu saja, mengidentifikasi apa yang harus dilakukan pada awalnya sangat penting, dan panel performa adalah alat yang tepat untuk hal ini.
Penting juga untuk menekankan bahwa angka-angka ini khusus untuk profil yang digunakan sebagai subjek studi. Profil tersebut menarik bagi kami karena ukurannya sangat besar. Namun demikian, karena pipeline pemrosesan sama untuk setiap profil, peningkatan signifikan yang dicapai berlaku untuk setiap profil yang dimuat di panel performa.
Poin-poin penting
Ada beberapa pelajaran yang dapat diambil dari hasil ini terkait pengoptimalan performa aplikasi Anda:
1. Gunakan alat pembuatan profil untuk mengidentifikasi pola performa runtime
Alat pembuatan profil sangat berguna untuk memahami apa yang terjadi di aplikasi saat dijalankan, terutama untuk mengidentifikasi peluang peningkatan performa. Panel Performa di Chrome DevTools adalah opsi yang bagus untuk aplikasi web karena merupakan alat pembuatan profil web native di browser, dan secara aktif dipertahankan agar selalu update dengan fitur platform web terbaru. Selain itu, kini jauh lebih cepat. 😉
Gunakan sampel yang dapat digunakan sebagai workload representatif dan lihat apa yang dapat Anda temukan.
2. Hindari hierarki panggilan yang rumit
Jika memungkinkan, hindari membuat grafik panggilan Anda terlalu rumit. Dengan hierarki panggilan yang rumit, regresi performa dapat dengan mudah terjadi dan sulit untuk memahami mengapa kode Anda berjalan seperti itu, sehingga sulit untuk mendapatkan peningkatan.
3. Mengidentifikasi pekerjaan yang tidak perlu
Codebase yang sudah lama biasanya berisi kode yang tidak lagi diperlukan. Dalam kasus kami, kode lama dan tidak perlu mengambil sebagian besar total waktu pemuatan. Menghapusnya adalah hal yang paling mudah dilakukan.
4. Menggunakan struktur data dengan tepat
Gunakan struktur data untuk mengoptimalkan performa, tetapi juga pahami biaya dan kompromi yang ditimbulkan oleh setiap jenis struktur data saat memutuskan struktur data yang akan digunakan. Hal ini bukan hanya kompleksitas ruang dari struktur data itu sendiri, tetapi juga kompleksitas waktu dari operasi yang berlaku.
5. Menyimpan hasil dalam cache untuk menghindari tugas duplikat untuk operasi yang kompleks atau berulang
Jika operasi mahal untuk dieksekusi, sebaiknya simpan hasilnya untuk saat berikutnya diperlukan. Hal ini juga masuk akal jika operasi dilakukan berkali-kali—meskipun setiap waktu individual tidak terlalu mahal.
6. Menunda pekerjaan yang tidak penting
Jika output tugas tidak diperlukan segera dan eksekusi tugas memperpanjang jalur kritis, pertimbangkan untuk menundanya dengan memanggilnya secara lambat saat outputnya benar-benar diperlukan.
7. Menggunakan algoritma yang efisien pada input besar
Untuk input besar, algoritma kompleksitas waktu yang optimal menjadi sangat penting. Dalam contoh ini, kita tidak membahas kategori ini, tetapi pentingnya tidak dapat dilebih-lebihkan.
8. Bonus: membandingkan performa pipeline Anda
Untuk memastikan kode Anda yang terus berkembang tetap cepat, sebaiknya pantau perilaku dan bandingkan dengan standar. Dengan begitu, Anda dapat mengidentifikasi regresi secara proaktif dan meningkatkan keandalan secara keseluruhan, sehingga Anda siap meraih kesuksesan jangka panjang.