CPU profile adalah rekaman function mana yang sedang dikerjakan CPU, dan berapa lama. Flame graph menggambarnya: tiap function jadi satu batang, lebarnya adalah waktu, dan batang yang bertumpuk menunjukkan siapa memanggil siapa. Bayangin laporan jam kerja satu tim. Manajer tercatat 100 jam karena semua kerjaan timnya dihitung di bawah namanya, padahal dia sendiri cuma kerja 5 jam. Mau proyeknya cepat selesai? Bantu orang yang jam kerjanya sendiri paling banyak, bukan yang namanya paling atas di laporan.
🧪 Tebakan cepat: Di profil halaman yang lambat, batang paling lebar adalah
main, 100 ms. Kalau kode di dalammainsendiri kamu bikin dua kali lebih cepat, halamannya jadi lebih cepat berapa ms? Jawabannya ada di bagian "Self time dan total time".
Merekam profil di Chrome DevTools
- Buka DevTools, pilih panel Performance.
- Klik tombol Record, lakukan hal yang terasa lambat (klik tombol, scroll, muat data), lalu klik Stop.
- Di track Main muncul flame chart. Di panel bawahnya ada tab Summary, Bottom-up, Call tree, dan Event log.
Profiler ini bekerja dengan sampling: berkali-kali tiap detik, dia mencatat function apa yang sedang jalan beserta pemanggilnya. Angkanya perkiraan yang sangat dekat, cukup untuk menemukan bagian yang lambat.
Cara baca flame chart
Ini profil yang dipakai di Lab dan di latihan. Halamannya butuh 100 ms:
| Batang | Dipanggil oleh | Mulai | Selesai | Lebar |
|---|---|---|---|---|
main |
0 ms | 100 ms | 100 ms | |
renderPage |
main |
2 ms | 62 ms | 60 ms |
parseJSON |
renderPage |
4 ms | 34 ms | 30 ms |
compile |
parseJSON |
20 ms | 25 ms | 5 ms |
loadAsset |
renderPage |
38 ms | 58 ms | 20 ms |
compile |
main |
64 ms | 99 ms | 35 ms |
- Di flame chart Chrome, sumbu mendatar adalah waktu. Lebar batang = berapa lama panggilan itu berlangsung, termasuk semua function yang dia panggil.
- Batang tepat di bawah sebuah batang adalah function yang dipanggil olehnya. Makin ke bawah, makin dalam call stack-nya (urutan function yang sedang saling memanggil).
- Bagian batang yang gak tertutup batang anaknya adalah waktu yang dipakai function itu sendiri.
- Warna cuma pembeda, bukan tanda bahaya.
Self time dan total time
- Total time: waktu dari function mulai sampai selesai, termasuk function yang dia panggil. Ini lebar batangnya.
- Self time: waktu di kode function itu sendiri saja. Total time dikurangi total time anak-anaknya.
Tab Bottom-up menyusunnya per function:
| Function | Total time | Self time |
|---|---|---|
main |
100 ms | 5 ms |
renderPage |
60 ms | 10 ms |
compile |
40 ms | 40 ms |
parseJSON |
30 ms | 25 ms |
loadAsset |
20 ms | 20 ms |
main: 100 - 60 - 35 = 5 ms. Dia lebar karena anak-anaknya, bukan karena kerjanya sendiri.compilemuncul di dua tempat (35 ms di bawahmain, 5 ms di bawahparseJSON). Bottom-up menjumlahkan semua tempatnya: 40 ms.- Jumlah semua self time = 5 + 10 + 40 + 25 + 20 = 100 ms, sama dengan total time
main. Tiap milidetik milik self time tepat satu function.
Jadi kode main sendiri cuma 5 ms. Dibikin dua kali lebih cepat pun cuma hemat 2,5 ms: 100 ms jadi 97,5 ms. Itu jawaban tebakannya.
Optimasi yang paling berat dulu
Hukum Amdahl bilang: percepatan total dibatasi oleh seberapa besar bagian yang kamu percepat. Function yang cuma memakai 5% waktu, dibikin instan pun cuma menghemat 5%.
| Dipercepat 2x | Self time | Hemat | Profil jadi |
|---|---|---|---|
compile |
40 ms | 20 ms | 80 ms |
parseJSON |
25 ms | 12,5 ms | 87,5 ms |
renderPage |
10 ms | 5 ms | 95 ms |
main |
5 ms | 2,5 ms | 97,5 ms |
renderPage punya total time 60 ms, tapi mempercepat kodenya sendiri cuma hemat 5 ms. Target pertama adalah self time tertinggi: compile, 40% dari seluruh waktu. Alurnya: rekam, cari self time tertinggi, perbaiki satu, lalu rekam ulang untuk membuktikan hasilnya.
Menganalisis profil dengan kode
Di latihan, profil Bottom-up tadi jadi array, dan kamu menulis empat function untuk membacanya:
const profile = [
{ name: "main", totalMs: 100, selfMs: 5 },
{ name: "renderPage", totalMs: 60, selfMs: 10 },
{ name: "parseJSON", totalMs: 30, selfMs: 25 },
{ name: "compile", totalMs: 40, selfMs: 40 },
{ name: "loadAsset", totalMs: 20, selfMs: 20 },
]
function topBySelf(profile, n) {
return [...profile] // salin dulu, sort mengubah array aslinya
.sort((a, b) => b.selfMs - a.selfMs) // terbesar dulu
.slice(0, n) // ambil n teratas
.map((f) => f.name) // namanya saja
}
function selfTimePercent(profile, name) {
const fn = profile.find((f) => f.name === name)
if (!fn) return 0
const totalSelf = profile.reduce((sum, f) => sum + f.selfMs, 0) // 100
return Math.round((fn.selfMs / totalSelf) * 100)
}
console.log(topBySelf(profile, 2).join(",")) // compile,parseJSON
console.log(selfTimePercent(profile, "compile")) // 40
console.log(selfTimePercent(profile, "unknown")) // 0
topByTotalsama persis, cuma memakaitotalMs:topByTotal(profile, 2)menghasilkanmain,renderPage.findHotspots(profile, minSelfMs)menyaring dulu denganfilter((f) => f.selfMs >= minSelfMs), baru mengurutkan dari self time terbesar dan mengambil namanya. Dengan batas 20 hasilnyacompile,parseJSON,loadAsset.selfTimePercentmembagi dengan jumlah self time semua function, bukan dengan total time function itu, lalu membulatkan denganMath.round. Nama yang gak ada mengembalikan 0.
🤔 Flame graph, flame chart, dan sampling
- Flame graph versi aslinya (Brendan Gregg): sumbu mendatar bukan waktu. Stack yang sama digabung, diurutkan alfabet, akarnya di bawah dan tumbuh ke atas. Lebar tetap berarti porsi total waktu.
- Flame chart di Chrome: sumbu mendatar adalah waktu dan akarnya di atas, jadi tumbuh ke bawah. Konsep bacanya sama.
- Sampling mencatat stack secara berkala, ringan tapi berupa perkiraan, dan function yang sangat singkat bisa gak tercatat. Instrumentation mencatat tiap function masuk dan keluar, tepat tapi membuat program lebih lambat.
- Di Node.js,
node --cpu-prof app.jsmenulis file.cpuprofileyang bisa dibuka di DevTools.
⚠️ Jebakan yang sering ketemu
- Mengira batang paling lebar pasti biang lambatnya.
mainpaling lebar, tapi self time-nya cuma 5 ms. Selalu cek self time. - Urutan terbalik.
a.selfMs - b.selfMsmengurutkan dari yang terkecil. Untuk terbesar dulu, pakaib.selfMs - a.selfMs. - Persen dari angka yang salah.
selfTimePercentdibagi jumlahselfMssemua function (100), lalu dibulatkan. Nama yang gak ada harus 0, bukanNaNatau error. - Optimasi tanpa profil. Tebakan developer soal bagian yang lambat sering meleset. Rekam dulu, dan rekam lagi setelah memperbaiki.
TL;DR: Rekam profil di panel Performance: Record, lakukan yang lambat, Stop. Di flame chart Chrome, sumbu mendatar adalah waktu, lebar batang adalah total time (termasuk function yang dipanggilnya), dan batang di bawah adalah yang dipanggil. Self time adalah waktu di kode function itu sendiri, total time dikurangi anak-anaknya, dan Bottom-up menjumlahkan function yang muncul di beberapa tempat. Hukum Amdahl: optimasi function dengan self time tertinggi dulu, karena hematnya paling besar (compile 40 ms, 40%), sedangkan main yang paling lebar cuma punya 5 ms. Di latihan: salin lalu urutkan menurun, ambil n nama, saring dengan >=, dan hitung persen dari jumlah self time dengan Math.round, dengan 0 untuk nama yang gak ada.