Klaim kecepatan pada modul ini diukur, tidak pernah sekadar dinyatakan. Topik ini menyediakan perangkatnya, yaitu package BenchmarkTools untuk mengukur waktu dan alokasi, makro @allocated untuk melacak byte yang dialokasikan satu pemanggilan, dan profiler dari pustaka standar Profile untuk menemukan bagian yang paling menyita waktu. Setelah membacanya, pengukuran dapat dilakukan dengan pola yang adil sehingga angkanya layak dipercaya.
Mengukur alokasi dengan BenchmarkTools#
Package BenchmarkTools menyediakan makro @benchmark dan @btime yang menjalankan ekspresi berkali-kali dengan pemanasan otomatis, lalu merangkum distribusi waktunya. Keluaran kedua makro memuat angka waktu yang berbeda pada setiap eksekusi, sehingga transkripnya tidak layak ditempel sebagai keluaran tetap. Bagian yang stabil justru jumlah alokasi dan byte memori per pemanggilan, dan itulah yang dicetak oleh contoh-contoh pada topik ini.
Skrip berikut membandingkan penjumlahan pada array Float64 dengan array Any yang isinya sama persis, yaitu perbandingan terukur untuk klaim stabilitas tipe pada bagian awal modul ini. Objek hasil benchmark disimpan pada variabel, lalu bagian deterministiknya dicetak dengan fungsi allocs dan memory.
# perbandingan terukur: penjumlahan pada Vector{Float64} vs Vector{Any}
using BenchmarkTools
data_f = collect(1.0:1000.0)
data_a = Any[data_f...]
# hangatkan agar kompilasi JIT tidak ikut terukur
sum(data_f)
sum(data_a)
bm_f = @benchmark sum($data_f)
bm_a = @benchmark sum($data_a)
println("sum pada Vector{Float64}: ", allocs(bm_f), " alokasi, ", memory(bm_f), " byte")
println("sum pada Vector{Any}: ", allocs(bm_a), " alokasi, ", memory(bm_a), " byte")sum pada Vector{Float64}: 0 alokasi, 0 byte
sum pada Vector{Any}: 999 alokasi, 15984 byteAngkanya tegas, yaitu penjumlahan pada Vector{Float64} tidak mengalokasikan apa pun, sedangkan penjumlahan pada Vector{Any} dengan isi yang sama mengalokasikan total 15984 byte dalam 999 kali alokasi. Setiap elemen bertipe Any memaksa hasil penjumlahan antar elemen dikemas ulang saat eksekusi, dan pengemasan itulah yang diperhitungkan BenchmarkTools. Dengan demikian, klaim bahwa tipe Any menambah kerja eksekusi terukur dalam byte dan jumlah alokasi, bukan sekadar kesan.
Interpolasi argumen dengan $#
Tanda dolar pada @benchmark sum($data) bukan dekorasi. Ekspresi benchmark dijalankan di dalam fungsi internal, dan tanpa $ variabel data dibaca sebagai variabel global pada setiap sampel, sehingga yang terukur ikut mencakup pencarian global beserta biayanya. Dengan $, nilai data ditempel langsung ke fungsi internal benchmark, sehingga yang diukur murni pekerjaan sum.
# tanpa interpolasi: data dibaca sebagai variabel global tiap sampel
using BenchmarkTools
data = collect(1.0:1000.0)
sum(data)
sum(data)
bm_tanpa = @benchmark sum(data)
bm_dengan = @benchmark sum($data)
println("tanpa interpolasi: ", allocs(bm_tanpa), " alokasi, ", memory(bm_tanpa), " byte")
println("dengan interpolasi: ", allocs(bm_dengan), " alokasi, ", memory(bm_dengan), " byte")tanpa interpolasi: 1 alokasi, 16 byte
dengan interpolasi: 0 alokasi, 0 byteKeluarannya memperlihatkan perbedaan yang mudah terlewat, yaitu versi tanpa $ mencatat satu alokasi 16 byte per sampel, sedangkan versi dengan $ mencatat nol. Selisih sekecil ini tampak remeh, tetapi pola yang sama pada fungsi yang dipanggil jutaan kali dapat mengarahkan perbaikan ke tempat yang keliru. Aturannya: interpolasikan seluruh argumen dengan $ pada setiap @benchmark dan @btime.
Melacak alokasi dengan @allocated#
Makro @allocated menjalankan ekspresi satu kali dan mengembalikan jumlah byte yang dialokasikan olehnya. Alat ini lebih kasar daripada BenchmarkTools karena tanpa distribusi waktu dan tanpa pengulangan, tetapi nyaris tanpa biaya pemasangan, sehingga cocok untuk pemeriksaan cepat di tengah pengembangan.
# hangatkan dulu, baru ukur alokasi satu pemanggilan
data = collect(1.0:100.0)
sum(data)
println(@allocated sum(data))0Nilai nol pada keluaran bermakna sum tidak mengalokasikan apa pun pada pemanggilan itu, tetapi nol itu hanya valid karena baris pemanasan sebelumnya sudah memicu kompilasi. Tanpa pemanasan, angka @allocated ikut menghitung alokasi yang terjadi selama kompilasi just-in-time, dan kesimpulannya menjadi menyesatkan. Pola yang sama berlaku pada BenchmarkTools, meskipun makro tersebut sudah melakukan pemanasan secara internal.
Mencatat profil eksekusi#
Benchmark menjawab berapa cepat satu fungsi, sedangkan profiler menjawab di mana waktu habis pada program yang utuh. Pustaka standar Profile bekerja dengan cara sampling, yaitu merekam tumpukan pemanggilan pada interval waktu tertentu selama eksekusi. Makro @profile membungkus ekspresi yang ingin dicatat, dan Profile.print menampilkan pohon sampel ke terminal.
# tidak-dijalankan
using Profile
jumlah(x) = (s = 0.0; for v in x; s += v; end; s)
data = collect(1.0:100000.0)
@profile jumlah(data)
Profile.print()Blok ini tidak dijalankan bersama contoh lain karena keluarannya memang tidak dapat direproduksi. Pohon sampel memuat jumlah sampel, alamat memori, dan berkas sumber yang berbeda pada setiap eksekusi, tergantung pada mesin dan beban sistem saat itu. Pola kerjanya tetap sama setiap saat: jalankan @profile pada beban kerja yang representatif, baca bagian pohon dengan sampel terbanyak, lalu ukur ulang fungsi yang dicurigai dengan BenchmarkTools.
Pola pengukuran yang adil#
Angka pengukuran hanya bermakna apabila kondisinya adil. Empat kebiasaan berikut menjaga keadilannya.
- Hangatkan fungsi sebelum mengukur, baik dengan satu pemanggilan manual untuk
@allocatedmaupun otomatis pada BenchmarkTools, agar waktu kompilasi just-in-time tidak ikut terukur. - Interpolasikan seluruh argumen dengan
$sehingga yang terukur murni pekerjaan fungsi, bukan pencarian variabel global. - Ukur di dalam fungsi yang menerima data melalui argumen, karena kode yang bekerja pada variabel global menyulitkan inferensi tipe sehingga hasilnya tidak mewakili kode yang sudah dirapikan.
- Bandingkan pada kondisi yang sama, yaitu mesin, versi Julia, dan beban sistem yang sama, lalu laporkan perbandingan relatif antar varian daripada angka waktu mentahnya.
Dengan pola ini, jumlah alokasi dan byte memori menjadi metrik yang paling layak dilaporkan karena nilainya tetap sama antar eksekusi, sedangkan waktu dipakai sebagai konteks. Klaim kecepatan pada modul ini mengikuti pola yang sama, yaitu setiap perbandingan disertai alokasinya, dan pembaca dapat mengulang pengukurannya pada mesin masing-masing.
Kesalahan umum#
Kesalahan pengukuran paling berbahaya karena angkanya tampak sah, padahal kondisinya timpang. Tiga kesalahan berikut perlu diwaspadai.
- Membandingkan angka waktu mentah antar mesin atau antar hari. Waktu mengikuti perangkat keras dan beban sistem, sedangkan jumlah alokasi tidak berubah, sehingga yang layak dilaporkan adalah alokasi dan perbandingan relatif pada mesin yang sama.
- Melupakan
$pada argumen benchmark. Pengukuran lalu ikut mencatat pencarian variabel global, sebagaimana contoh terukur pada bagian interpolasi. - Mengukur ekspresi yang berdiri di tingkat atas skrip dengan variabel global. Bungkus pekerjaan dalam fungsi dan beri data melalui argumen sebelum mengukur, sehingga angkanya mencerminkan kode yang akan dipakai.
Ringkasan#
- BenchmarkTools menjalankan ekspresi berkali-kali dengan pemanasan otomatis, dan bagian keluarannya yang stabil adalah jumlah alokasi serta byte memori per pemanggilan.
- Tanda
$menginterpolasi nilai argumen ke benchmark, sedangkan tanpa$pencarian variabel global ikut terukur. @allocatedmelaporkan byte alokasi satu pemanggilan setelah pemanasan, sehingga cocok untuk pemeriksaan cepat.- Profiler dari pustaka Profile menemukan bagian yang paling menyita waktu pada program utuh, dengan keluaran pohon sampel yang berbeda pada setiap eksekusi.
Referensi#
- BenchmarkTools.jl — juliaci
— Dokumentasi resmi makro
@benchmark,@btime, dan interpolasi$. - Profiling — Julia Documentation — Dokumentasi resmi profiler sampling pustaka standar.
- Performance Tips — Julia Documentation — Panduan resmi pola pengukuran dan sifat kode yang cepat.