Optimasi tanpa pengukuran hanyalah tebakan. Setelah Chapter-Chapter sebelumnya menunjukkan pola yang cepat dan pola yang lambat, Chapter ini membahas cara mengukurnya dengan benar: magic %timeit untuk lingkungan Jupyter, modul timeit untuk skrip, dan time.perf_counter() sebagai stopwatch resolusi tinggi. Satu hal yang sengaja kita hindari di buku ini: mencetak angka durasi mentah. Durasi berubah-ubah antar mesin, antar beban CPU, bahkan antar eksekusi di mesin yang sama — jadi skrip di bawah mengukur sungguhan, tetapi mencetak struktur hasil yang deterministik: jenis nilai, kebenaran hasil, jumlah sampel, dan jumlah pemanggilan.
Tiga Alat Ukur, Tiga Lingkungan #
| Alat | Lingkungan | Bentuk hasil | Catatan |
|---|---|---|---|
%timeit / %time | Jupyter / IPython | ringkasan statistik di sel | tidak berjalan di skrip .py |
timeit.repeat() | skrip | list berisi durasi per sampel | pilih min() sebagai estimasi terbaik |
time.perf_counter() | skrip | satu durasi per pengukuran | stopwatch umum, resolusi tinggi |
cProfile | skrip | laporan durasi per fungsi | untuk mencari titik lambat di program besar |
Mengukur Loop vs Vectorized #
Skrip berikut mengukur dua fungsi pada 100.000 baris — satu dengan loop itertuples, satu vectorized — lalu mencetak struktur hasilnya, bukan angka durasinya:
import time
import timeit
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 100_000
df = pd.DataFrame(
{
"jumlah": rng.integers(1, 11, size=n),
"harga": rng.choice([15000, 25000, 40000, 55000, 70000], size=n),
}
)
# 1. Pola pengukuran dengan time.perf_counter()
def ukur(fungsi):
mulai = time.perf_counter()
hasil = fungsi()
durasi = time.perf_counter() - mulai
return hasil, durasi
def hitung_loop():
hasil = []
for baris in df.itertuples():
hasil.append(baris.jumlah * baris.harga)
return hasil
def hitung_vectorized():
return (df["jumlah"] * df["harga"]).to_numpy()
hasil_loop, durasi_loop = ukur(hitung_loop)
hasil_vec, durasi_vec = ukur(hitung_vectorized)
print("perf_counter mengembalikan durasi float:", isinstance(durasi_loop, float))
print("kedua durasi positif:", durasi_loop > 0, durasi_vec > 0)
print("hasil loop == hasil vectorized:", np.array_equal(hasil_loop, hasil_vec))
print()
# 2. timeit.repeat: beberapa sampel pengulangan
waktu_vec = timeit.repeat(hitung_vectorized, number=5, repeat=3)
waktu_loop = timeit.repeat(hitung_loop, number=1, repeat=3)
print("timeit.repeat(number=5, repeat=3) menghasilkan:", len(waktu_vec), "nilai")
print("semua nilai > 0:", all(w > 0 for w in waktu_vec))
print("praktik: bandingkan min() antar pola, bukan satu nilai tunggal")
print("jumlah baris yang diukur:", f"{n:,}")
print()
# 3. Biaya yang terukur secara deterministik: jumlah pemanggilan
print("pemanggilan Python versi loop :", f"{n:,}")
print("pemanggilan Python versi vectorized : 0")Output yang Dihasilkan:
perf_counter mengembalikan durasi float: True
kedua durasi positif: True True
hasil loop == hasil vectorized: True
timeit.repeat(number=5, repeat=3) menghasilkan: 3 nilai
semua nilai > 0: True
praktik: bandingkan min() antar pola, bukan satu nilai tunggal
jumlah baris yang diukur: 100,000
pemanggilan Python versi loop : 100,000
pemanggilan Python versi vectorized : 0Mari kita telaah baris-baris pentingnya:
- Baris 18-22 (helper
ukur()): fungsi ini membungkus polaperf_counter— catat waktu sebelum, jalankan fungsi, catat waktu sesudah, kembalikan hasil beserta durasinya. Pemisahan hasil dan durasi memudahkan pemeriksaan kebenaran di kemudian hari. - Baris 25-33 (dua kandidat):
hitung_loop()mengalikan per baris lewatitertuples, sedangkanhitung_vectorized()mengalikan dua Series dalam satu pemanggilan lalu mengubahnya ke array agar perbandingannya adil. - Baris 36-40 (pengukuran pertama): kedua durasi adalah
floatdan bernilai positif (True), dan hasil kedua fungsinp.array_equal— sama persis. Pengukuran yang tidak menguji kebenaran hasil bisa saja mengukur kode yang salah. - Baris 44-49 (
timeit.repeat):number=5berarti setiap sampel menjalankan fungsi lima kali, danrepeat=3menghasilkan tiga sampel — jadi list berisi 3 nilai. Semua bernilai positif (True); pada praktiknya kita membandingkanmin()antar pola, bukan satu nilai tunggal, karena sampel terendah paling bebas interferensi. - Baris 53-54 (biaya deterministik): versi loop menjalankan 100.000 pemanggilan Python, versi vectorized tidak melakukan pemanggilan Python sama sekali. Selisih jumlah pemanggilan ini yang tetap benar di mesin mana pun — dan pada pengukuran kami, versi loop selesai ordo puluhan kali lebih lama.
Disiplin Pengukuran #
Empat kebiasaan membuat hasil pengukuran bisa dipercaya. Pertama, bekuakan data: gunakan seed tetap atau data yang sama persis untuk semua kandidat, sebab membandingkan operasi pada data berbeda adalah perbandingan yang tidak bermakna. Kedua, ulangi beberapa sampel dan bandingkan min(), bukan rata-rata — nilai terendah paling dekat dengan kemampuan asli kode. Ketiga, ukur pada ukuran data yang sama dan laporkan ukuran itu; ordo hasil berubah sesuai skala. Keempat, jangan bandingkan angka antar mesin — angka durasi hanya bermakna dalam satu lingkungan yang sama.
Dengan disiplin pengukuran ini, semua teknik Section ini punya dasar yang jelas: ukur, ganti pola Python-level dengan vectorized, tekan memori lewat dtype, lalu ukur lagi. Chapter berikutnya menutup rangkaian penghematan dengan dtype yang paling menguntungkan untuk kolom teks: category beserta penghitungan penghematannya.
Referensi resmi untuk pendalaman: panduan Enhancing performance dan panduan Scaling to large datasets .