Topik ini menutup proyek analisis kisi suhu ruangan dengan laporan benchmark, yaitu pengukuran kinerja yang disusun sebagai bukti. Pembahasan mencakup metodologi pengukuran yang jujur, hasil khas dari eksekusi nyata pada dua ukuran kisi, verifikasi kebenaran sebagai bagian tetap laporan, dan tabel perbandingan yang merangkum waktu, jumlah baris kode, serta risiko setiap versi. Setelah topik ini selesai, syarat keenam definisi selesai terpenuhi dan proyek memiliki bukti kinerja yang dapat diperiksa ulang.
Metodologi pengukuran#
Kepercayaan sebuah laporan benchmark ditentukan oleh metodologinya sebelum angkanya dibaca. Prinsip yang dipakai di sini ada empat. Pertama, waktu diukur dengan time.perf_counter(), yaitu jam beresolusi tinggi yang memang dirancang untuk mengukur selisih waktu. Kedua, setiap fungsi dipanaskan satu kali (warm-up) sebelum diukur, karena eksekusi pertama memuat pekerjaan sekali jalan yang tidak muncul lagi setelahnya. Ketiga, setiap versi diukur sembilan kali dan yang dilaporkan mediannya, karena median tidak tergeser oleh satu pengukuran yang kebetulan lambat. Keempat, kedua versi mengolah data yang sama persis dari seed 42, sehingga selisih waktunya murni berasal dari gaya komputasinya.
Skrip pengukurannya disimpan sebagai benchmark_suhu.py. Skrip ini tidak diberi blok keluaran pada artikel karena angkanya berubah antar mesin dan antar eksekusi, tetapi isinya dapat dijalankan pembaca pada lingkungannya masing-masing.
# tidak-dijalankan
# skrip pengukuran waktu: keluarannya berubah antar mesin, sehingga artikel tidak menyertakan keluarannya
"""Benchmark proyek kisi suhu: perulangan Python versus kode tervektorisasi.
Setiap tahap dipanaskan sekali, lalu diukur beberapa kali dengan
time.perf_counter. Yang dilaporkan adalah median waktu setiap versi, bukan
satu kali pengukuran terbaik.
"""
import time
import numpy as np
def buat_suhu(jam, sensor):
rng = np.random.default_rng(42)
offset = rng.uniform(-1.5, 1.5, size=sensor)
derau = rng.normal(0, 0.8, size=(jam, sensor))
baris = np.arange(jam)[:, np.newaxis]
return np.round(22.0 + 3.0 * np.sin((baris - 6) * np.pi / 12) + offset + derau, 1)
def normalisasi_vektor(suhu):
return (suhu - suhu.mean(axis=0, keepdims=True)) / suhu.std(axis=0, keepdims=True)
def ringkasan_vektor(suhu):
return suhu.mean(axis=1)
def jarak_vektor(z):
selisih = z.T[:, np.newaxis, :] - z.T[np.newaxis, :, :]
return np.sqrt((selisih ** 2).sum(axis=-1))
def normalisasi_perulangan(suhu):
n_jam, n_sensor = suhu.shape
z = np.zeros((n_jam, n_sensor))
for s in range(n_sensor):
total = 0.0
for j in range(n_jam):
total += suhu[j, s]
rata_s = total / n_jam
kuadrat = 0.0
for j in range(n_jam):
kuadrat += (suhu[j, s] - rata_s) ** 2
simpangan_s = (kuadrat / n_jam) ** 0.5
for j in range(n_jam):
z[j, s] = (suhu[j, s] - rata_s) / simpangan_s
return z
def ringkasan_perulangan(suhu):
n_jam, n_sensor = suhu.shape
rata = np.zeros(n_jam)
for j in range(n_jam):
total = 0.0
for s in range(n_sensor):
total += suhu[j, s]
rata[j] = total / n_sensor
return rata
def jarak_perulangan(z):
n_jam, n_sensor = z.shape
d = np.zeros((n_sensor, n_sensor))
for i in range(n_sensor):
for k in range(n_sensor):
total = 0.0
for j in range(n_jam):
total += (z[j, i] - z[j, k]) ** 2
d[i, k] = total ** 0.5
return d
def ukur(fungsi, ulangan=9):
fungsi()
catatan = []
for _ in range(ulangan):
mulai = time.perf_counter()
fungsi()
catatan.append(time.perf_counter() - mulai)
catatan.sort()
return catatan[len(catatan) // 2]
for jam, sensor in [(24, 8), (168, 24)]:
suhu = buat_suhu(jam, sensor)
z = normalisasi_vektor(suhu)
print(f"kisi {jam} x {sensor} ({jam * sensor} sel)")
tugas = [
("normalisasi", normalisasi_perulangan, normalisasi_vektor, (suhu,)),
("ringkasan", ringkasan_perulangan, ringkasan_vektor, (suhu,)),
("jarak", jarak_perulangan, jarak_vektor, (z,)),
]
for nama, f_loop, f_vektor, argumen in tugas:
t_loop = ukur(lambda: f_loop(*argumen))
t_vek = ukur(lambda: f_vektor(*argumen))
print(f" {nama:12} perulangan {t_loop * 1000:9.3f} ms | "
f"tervektorisasi {t_vek * 1000:8.3f} ms | {t_loop / t_vek:5.1f}x")Kondisi lingkungan pengukuran dilaporkan apa adanya, yaitu satu sesi Python 3.12 dengan NumPy 2.1.3 pada mesin penulisan dengan beban normal, tanpa penyetelan khusus. Angka pada lingkungan lain akan berbeda, sehingga yang layak dibandingkan dari laporan semacam ini adalah urutan besarnya, bukan angka persisnya.
Hasil pengukuran#
Tabel berikut merangkum waktu khas dari beberapa eksekusi skrip pada lingkungan penulisan, dengan kisi proyek berukuran 192 sel.
| Tahap | Perulangan | Tervektorisasi | Selisih khas |
|---|---|---|---|
| Normalisasi per sensor | 0.10 ms | 0.02 ms | sekitar 5 kali |
| Ringkasan per jam | 0.03 ms | 0.005 ms | sekitar 5 kali |
| Jarak antar sensor | 0.41 ms | 0.01 ms | 35 sampai 48 kali |
Pada kisi kecil 192 sel, selisih normalisasi dan ringkasan berada di sekitar lima kali, terasa tetapi tidak dramatis, karena waktu versi tervektorisasi masih didominasi overhead tetap seperti pemanggilan fungsi dan penyiaran bentuk, bukan perhitungan datanya. Selisih jarak antar sensor sudah mencapai puluhan kali pada kisi kecil karena versi perulangannya menumpuk tiga perulangan untuk setiap pasangan sensor. Pola normalisasi dan ringkasan berubah ketika kisi diperbesar menjadi 168 jam dan 24 sensor, yaitu 4032 sel atau 21 kali lebih banyak. Pada ukuran tersebut, normalisasi terukur sekitar 1.95 ms berbanding 0.04 ms, ringkasan sekitar 0.46 ms berbanding 0.009 ms, dan jarak sekitar 24.6 ms berbanding 0.52 ms, sehingga selisih ketiga tahap berkumpul di sekitar 50 kali. Biaya tetap versi tervektorisasi tidak tumbuh secepat biaya per elemen versi perulangan, sehingga makin banyak data, makin lebar selisih keduanya.
Perbedaan gaya komputasinya juga dapat dibaca langsung dari kodenya. Versi perulangan melakukan sekitar 576 operasi skalar untuk normalisasi, 192 untuk ringkasan, dan 1536 untuk matriks jarak pada kisi proyek, sedangkan versi tervektorisasi menyelesaikan setiap tahap dengan satu rangkaian pemanggilan array. Angka-angka ini bukan hasil pengukuran, melainkan konsekuensi struktur kode yang waktunya diukur di atas.
Verifikasi kebenaran dalam laporan#
Angka waktu tanpa bukti kebenaran tidak layak dilaporkan, karena versi tercepat tidak berarti apa pun apabila hasilnya salah. Laporan benchmark proyek ini karenanya menyertakan verifikasi yang sama dengan definisi selesai, yaitu perbandingan np.allclose antara kedua versi pada ketiga tahap, dieksekusi pada data yang sama dengan data yang diukur.
import numpy as np
JAM, SENSOR = 24, 8
rng = np.random.default_rng(42)
offset = rng.uniform(-1.5, 1.5, size=SENSOR)
derau = rng.normal(0, 0.8, size=(JAM, SENSOR))
jam = np.arange(JAM)[:, np.newaxis]
suhu = np.round(22.0 + 3.0 * np.sin((jam - 6) * np.pi / 12) + offset + derau, 1)
z = (suhu - suhu.mean(axis=0, keepdims=True)) / suhu.std(axis=0, keepdims=True)
ringkas = suhu.mean(axis=1)
selisih = z.T[:, np.newaxis, :] - z.T[np.newaxis, :, :]
jarak = np.sqrt((selisih ** 2).sum(axis=-1))
z_loop = np.zeros((JAM, SENSOR))
for s in range(SENSOR):
total = 0.0
for j in range(JAM):
total += suhu[j, s]
rata_s = total / JAM
kuadrat = 0.0
for j in range(JAM):
kuadrat += (suhu[j, s] - rata_s) ** 2
simpangan_s = (kuadrat / JAM) ** 0.5
for j in range(JAM):
z_loop[j, s] = (suhu[j, s] - rata_s) / simpangan_s
ringkas_loop = np.zeros(JAM)
for j in range(JAM):
total = 0.0
for s in range(SENSOR):
total += suhu[j, s]
ringkas_loop[j] = total / SENSOR
jarak_loop = np.zeros((SENSOR, SENSOR))
for i in range(SENSOR):
for k in range(SENSOR):
total = 0.0
for j in range(JAM):
total += (z[j, i] - z[j, k]) ** 2
jarak_loop[i, k] = total ** 0.5
print("normalisasi per sensor :", np.allclose(z_loop, z))
print("ringkasan per jam :", np.allclose(ringkas_loop, ringkas))
print("matriks jarak :", np.allclose(jarak_loop, jarak))
print("ukuran data suhu :", suhu.nbytes, "byte")
print("ukuran array selisih :", selisih.nbytes, "byte")normalisasi per sensor : True
ringkasan per jam : True
matriks jarak : True
ukuran data suhu : 1536 byte
ukuran array selisih : 12288 byteKetiga pemeriksaan mengembalikan True, sehingga selisih waktu pada tabel sebelumnya memang membandingkan dua versi yang menghasilkan nilai yang sama. Dua baris terakhir menambahkan bukti memori yang bersifat deterministik, yaitu data proyek menempati 1536 byte, sedangkan array selisih tahap jarak menempati 12288 byte atau delapan kali lipatnya. Besar memori antara layak dilaporkan karena pola broadcasting tiga dimensi menumbuhkan array antara sebesar hasil kali ketiga dimensinya, dan angka ini ikut menentukan apakah pola tersebut masih masuk akal pada data yang lebih besar.
Tabel perbandingan dan risiko#
Rangkuman perbandingan kedua versi disusun dalam satu tabel, dengan jumlah baris kode dihitung dari berkas benchmark_suhu.py, yaitu baris nonkosong setiap fungsi tanpa baris def-nya. Dengan hitungan itu, versi perulangan menyumbang 31 baris dan versi tervektorisasi menyumbang 4 baris, sebagaimana tercantum pada tabel.
| Aspek | Versi perulangan | Versi tervektorisasi |
|---|---|---|
| Baris kode ketiga tahap | 31 | 4 |
| Jumlah waktu khas ketiga tahap pada kisi proyek | 0.54 ms | 0.035 ms |
| Sumber kesalahan yang umum | indeks tertukar dan batas perulangan salah | bentuk broadcasting dan memori antara |
Kesimpulan teknisnya untuk proyek ini adalah versi tervektorisasi lebih cepat dan lebih ringkas sekaligus, dengan kebenaran yang terverifikasi. Versi perulangan tetap dipertahankan sebagai pembanding kebenaran dan sebagai bentuk yang paling mudah ditulis ketika logika per elemen dikerjakan pertama kali. Apabila polanya tidak dapat diseragamkan atau memori antaranya melebihi batas yang tersedia, bentuk perulangan atau campuran keduanya tetap menjadi pilihan yang sah.
Risiko kedua bentuk berbeda jenisnya. Versi perulangan rawan indeks yang tertukar antara baris dan kolom serta rawan batas perulangan yang meleset satu, dan kerentanannya tumbuh seiring panjang kodenya. Versi tervektorisasi rawan kesalahan bentuk broadcasting yang kadang tidak muncul sebagai error, sebagaimana contoh penukaran sumbu pada tahap implementasi proyek ini, serta konsumsi memori antara yang tumbuh cepat pada pola tiga dimensi. Kedua risiko itu ditangani dengan cara yang sudah dipakai sepanjang proyek, yaitu memeriksa bentuk setiap tahap dan memverifikasi hasilnya dengan np.allclose.
Kesalahan umum#
Kesalahan pertama adalah melaporkan hasil satu kali pengukuran. Satu eksekusi dapat bertepatan dengan gangguan dari proses lain, sehingga angkanya dapat dua kali lipat dari kondisi biasanya. Perbaikannya adalah pemanasan dan pengulangan yang diterapkan metodologi di atas, dengan median sebagai pelaporannya.
Kesalahan kedua adalah memasukkan pembangkitan data ke dalam waktu yang diukur. Pembangkitan data memakan waktu yang sama untuk kedua versi, sehingga menyertainya mengecilkan selisih yang sebenarnya. Perbaikannya adalah membangkitkan data terlebih dahulu, lalu mengukur hanya perhitungan yang hendak dibandingkan, sebagaimana dilakukan benchmark_suhu.py.
Kesalahan ketiga adalah membandingkan angka dari lingkungan yang berbeda, baik antara dua mesin maupun antara dua sesi pada mesin yang sama dengan beban yang berbeda. Angka dari lingkungan yang berbeda tidak sebanding karena kecepatan prosesor, memori, dan beban sistem ikut berperan. Perbaikannya adalah mengukur kedua versi berdampingan pada lingkungan yang sama, lalu melaporkan kondisi lingkungannya bersama angkanya.
Ringkasan#
- Pengukuran memakai
time.perf_counter(), pemanasan sekali, sembilan kali pengulangan, dan median sebagai pelaporannya, pada data yang sama untuk kedua versi. - Pada kisi proyek 192 sel, selisih khasnya sekitar lima kali untuk normalisasi dan ringkasan serta puluhan kali untuk jarak, dan melebar ke sekitar 50 kali pada kisi 4032 sel.
- Setiap angka waktu disertai verifikasi
np.allcloseyang mengembalikanTrue, sehingga perbandingannya sah. - Versi tervektorisasi unggul pada waktu dan jumlah baris kode, sedangkan versi perulangan tetap berguna sebagai pembanding kebenaran.
Referensi#
- numpy.allclose — NumPy — Dokumentasi resmi fungsi verifikasi yang menjadi bagian tetap laporan benchmark ini.
- numpy.mean — NumPy — Dokumentasi agregasi rata-rata yang diukur pada tahap ringkasan per jam.
- Broadcasting — NumPy — Aturan penyiaran bentuk yang dipakai tahap jarak yang diukur dan diverifikasi.