Saat tim mengembangkan struktur data cepat atau komponen berorientasi performa, masalah utamanya bukan sekadar bagaimana menjalankan benchmark, tetapi bagaimana membuat hasil benchmark cukup stabil untuk dipakai sebagai sinyal CI. Tanpa kontrol lingkungan, baseline yang konsisten, dan aturan evaluasi yang jelas, angka benchmark di CI mudah berubah karena noise, throttling CPU, atau variasi runner, bukan karena perubahan kode.
Dalam konteks OpenZL sebagai proyek yang mengevaluasi struktur data, algoritma, atau library performa, pendekatan yang paling berguna adalah memperlakukan benchmark sebagai bagian dari developer tooling dan automation. Artinya, benchmark tidak dijalankan seragam di semua event, melainkan dipisah menjadi smoke benchmark untuk validasi cepat dan full benchmark untuk analisis tren dan regresi yang lebih akurat.
Mengapa CI benchmark sering tidak stabil
CI benchmark gagal menjadi alat pengambilan keputusan ketika tim menganggapnya sama dengan unit test. Unit test biasanya bersifat biner: lulus atau gagal. Benchmark berbeda, karena hasilnya dipengaruhi banyak variabel di luar kode aplikasi.
Sumber noise yang paling umum
- Runner bersama: mesin CI dipakai banyak job lain sehingga CPU, cache, dan I/O tidak konsisten.
- Dynamic CPU scaling: frekuensi CPU berubah tergantung beban dan kebijakan host.
- Warmup tidak memadai: runtime, allocator, JIT, atau cache belum stabil saat pengukuran dimulai.
- Dataset berubah: ukuran input, distribusi data, atau seed acak tidak dikunci.
- Perubahan dependency/toolchain: update compiler, runtime, atau library memengaruhi hasil.
- Metrik terlalu sempit: hanya menyimpan satu angka rata-rata tanpa variansi, p95, atau jumlah iterasi.
Karena itu, target realistis dari CI benchmark bukan menghasilkan angka yang identik di setiap run, melainkan menghasilkan sinyal yang cukup repeatable untuk mendeteksi perubahan bermakna.
Prinsip desain benchmark yang repeatable
1. Uji skenario yang representatif, bukan sintetis berlebihan
Untuk evaluasi struktur data cepat di OpenZL, benchmark sebaiknya merepresentasikan operasi yang benar-benar penting: misalnya insert, lookup, iterasi, merge, atau update pada ukuran dataset tertentu. Hindari benchmark yang terlalu kecil sehingga overhead framework benchmark lebih dominan daripada operasi inti.
Praktiknya, pilih beberapa profil input yang jelas:
- Kecil untuk smoke benchmark di PR.
- Menengah untuk validasi harian.
- Besar untuk release atau investigasi performa.
2. Kunci input dan sumber acak
Jika benchmark menggunakan data acak, gunakan seed tetap dan simpan parameter generator. Tujuannya bukan menghilangkan semua variasi, tetapi memastikan perubahan hasil tidak berasal dari distribusi input yang berubah antar-run.
benchmark_case: map_lookup_dense
seed: 42
input_size: 100000
key_distribution: uniform
iterations: 30
warmup_iterations: 103. Pisahkan warmup dan measurement
Warmup penting terutama bila ada cache, inisialisasi allocator, atau optimasi runtime. Jalankan beberapa iterasi warmup yang tidak dihitung, lalu baru ukur. Jika framework benchmark yang dipakai sudah menyediakan fase warmup, manfaatkan itu dan jangan mencampur logika warmup ke hasil final.
4. Ukur lebih dari satu metrik
Untuk struktur data dan library performa, satu metrik tidak cukup. Minimal simpan:
- Waktu eksekusi per operasi atau total skenario.
- Throughput bila relevan.
- Variansi atau simpangan baku.
- Jumlah sampel dan iterasi.
- Ukuran input.
- Informasi environment seperti commit, branch, runner label, dan toolchain.
Tanpa metadata ini, hasil benchmark sulit dibandingkan secara adil.
5. Gunakan perbandingan relatif, bukan angka absolut saja
Angka absolut dari CI sering dipengaruhi mesin. Karena itu, keputusan lulus/gagal lebih aman bila memakai perubahan relatif terhadap baseline yang setara, misalnya terhadap commit utama terakhir pada runner dan profil benchmark yang sama.
Arsitektur workflow CI benchmark yang stabil
Untuk repo kecil sampai menengah, workflow yang praktis biasanya dibagi menjadi tiga lapis:
- PR benchmark: cepat, murah, fokus deteksi kasar.
- Nightly benchmark: lebih lengkap, dipakai untuk tren dan investigasi regresi.
- Release benchmark: hasil yang lebih ketat untuk dokumentasi performa dan validasi sebelum rilis.
Smoke benchmark di Pull Request
Smoke benchmark bertujuan menjawab: apakah perubahan ini jelas lebih lambat atau merusak karakteristik dasar? Jangan jalankan seluruh matriks benchmark di setiap PR, karena itu mahal dan justru meningkatkan noise akibat antrean panjang.
Karakteristik smoke benchmark:
- Dataset kecil-menengah.
- Jumlah iterasi terbatas.
- Hanya skenario inti.
- Waktu eksekusi singkat.
- Threshold regresi longgar tapi berguna, misalnya untuk mendeteksi penurunan besar.
Full benchmark di Nightly
Nightly benchmark cocok untuk menghasilkan data yang lebih stabil karena tidak terikat latensi feedback PR. Di sini tim bisa menjalankan lebih banyak iterasi, beberapa ukuran input, dan mungkin runner yang lebih terkontrol. Hasil nightly idealnya disimpan agar bisa divisualisasikan per commit atau per hari.
Benchmark saat release
Release benchmark dipakai ketika tim memerlukan snapshot performa yang lebih dapat dipertanggungjawabkan. Misalnya sebelum menandai versi yang memperkenalkan struktur data baru di OpenZL, tim ingin membandingkan implementasi lama vs baru pada konfigurasi yang sama. Pada tahap ini, benchmark sebaiknya berjalan pada runner khusus atau self-hosted agar baseline lebih stabil.
Kontrol variabel lingkungan di CI
Jika tujuan Anda adalah membuat CI benchmark stabil, bagian ini lebih penting daripada pemilihan framework benchmark itu sendiri.
Prioritaskan runner yang konsisten
Runner self-hosted atau dedicated biasanya lebih stabil dibanding shared runner. Jika itu belum memungkinkan, setidaknya gunakan label runner yang sama untuk benchmark, dan jangan campur hasil dari kelas mesin yang berbeda ke dalam baseline yang sama.
Kunci toolchain dan dependency
Simpan informasi compiler, runtime, dependency lockfile, dan opsi build. Jika toolchain berubah, tandai sebagai baseline baru. Membandingkan benchmark lintas toolchain tanpa penanda khusus sering menghasilkan alarm palsu.
Kurangi pekerjaan lain dalam job benchmark
Jangan mencampur benchmark dengan lint, test, packaging, dan upload artifact berat dalam satu job jika tidak perlu. Benchmark sebaiknya dijalankan setelah build siap, sehingga gangguan I/O dan CPU lain minimal.
Gunakan mode build yang konsisten
Pastikan mode optimasi build yang dipakai selalu sama. Perbedaan mode debug dan release dapat membuat benchmark tidak bermakna. Selain itu, cache build memang berguna untuk mempercepat pipeline, tetapi jangan sampai cache membuat hasil benchmark sulit direproduksi atau tidak jelas asal binarinya.
Simpan metadata environment
Minimal sertakan metadata berikut di setiap hasil:
- commit SHA
- branch atau tag
- event CI: PR, nightly, release
- runner label atau machine class
- OS dan arsitektur
- toolchain atau runtime
- timestamp
Metadata ini penting saat tim menyelidiki regresi yang terlihat hanya pada subset environment tertentu.
Baseline, threshold regresi, dan keputusan lulus/gagal
Jangan jadikan setiap fluktuasi sebagai kegagalan
Kesalahan umum adalah menolak PR hanya karena benchmark berubah 1-2%. Pada kebanyakan CI umum, perubahan sekecil itu sering tidak cukup dapat dipercaya. Lebih aman menggunakan threshold yang disesuaikan dengan stabilitas runner dan pentingnya benchmark.
Pilih baseline yang eksplisit
Beberapa opsi baseline yang umum:
- Commit utama terakhir: sederhana, cocok untuk PR comparison.
- Median beberapa run nightly terbaru: lebih tahan noise.
- Baseline per release: cocok untuk laporan performa jangka panjang.
Untuk repo kecil-menengah, pendekatan yang praktis adalah:
- PR membandingkan hasil terhadap baseline dari branch utama terbaru.
- Nightly menyegarkan baseline statistik menggunakan beberapa run terakhir.
- Release memakai baseline yang dibekukan untuk versi target.
Gunakan dua level threshold
Model yang cukup efektif:
- Warning threshold: perubahan dicatat atau dikomentari di PR, tetapi tidak memblokir merge.
- Fail threshold: perubahan cukup besar sehingga job gagal.
Ini membantu mengurangi false positive sekaligus tetap memberi visibilitas dini.
Contoh kebijakan: smoke benchmark di PR hanya gagal jika regresi melewati ambang besar dan konsisten pada metrik inti. Perubahan kecil cukup diberi komentar agar reviewer bisa mengecek konteks perubahan.
Format output metrik yang mudah diautomasi
Hasil benchmark sebaiknya tidak hanya berupa teks bebas di log. Gunakan format terstruktur agar mudah diparsing, dibandingkan, disimpan, dan divisualisasikan.
Contoh format JSON
{
"suite": "openzl-data-structures",
"scenario": "map_lookup_dense",
"commit": "abc1234",
"event": "pull_request",
"runner": "self-hosted-x86_64",
"toolchain": "pinned",
"input_size": 100000,
"warmup_iterations": 10,
"measurement_iterations": 30,
"metrics": {
"time_ns_mean": 1250000,
"time_ns_stddev": 43000,
"throughput_ops_per_sec": 800000,
"samples": 30
}
}Jika satu job menghasilkan banyak skenario, simpan sebagai array JSON atau NDJSON agar mudah diproses per baris.
Kolom minimum yang sebaiknya ada
- nama suite dan skenario
- parameter input
- metrik utama dan variansi
- commit dan branch
- jenis pipeline
- runner dan toolchain
- status perbandingan terhadap baseline
Penyimpanan hasil dan visualisasi tren
Tanpa penyimpanan historis, benchmark CI hanya memberi snapshot sesaat. Padahal keputusan performa yang baik hampir selalu membutuhkan konteks tren.
Pilihan penyimpanan yang realistis
- Artifact CI: paling mudah, cocok untuk investigasi jangka pendek.
- Branch atau folder khusus hasil benchmark: sederhana, tetapi perlu disiplin agar repo tidak bengkak.
- Object storage atau database time-series: lebih rapi untuk tren jangka panjang.
- GitHub Pages atau dashboard statis: praktis untuk visualisasi jika data sudah diekspor ke JSON/CSV.
Untuk repo kecil-menengah, kombinasi yang sering cukup adalah:
- Artifact CI untuk setiap run.
- Job nightly yang menggabungkan hasil ke file historis terstruktur.
- Dashboard sederhana yang memplot waktu per skenario terhadap commit atau tanggal.
Apa yang perlu divisualisasikan
- tren mean atau median per skenario
- rentang variansi
- perubahan setelah merge tertentu
- perbandingan beberapa implementasi
- regresi yang berulang pada runner tertentu
Visualisasi sederhana pun cukup berguna asalkan konsisten. Tujuan utamanya adalah membantu tim membedakan anomali sesaat dari tren penurunan nyata.
Contoh workflow GitHub Actions
Contoh berikut menunjukkan pemisahan smoke benchmark untuk PR dan full benchmark untuk jadwal malam. Nama script dan command dibuat generik agar bisa disesuaikan dengan tooling benchmark yang dipakai tim OpenZL.
name: benchmark
on:
pull_request:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
push:
tags:
- 'v*'
jobs:
smoke-benchmark:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup toolchain
run: ./ci/setup-toolchain.sh
- name: Build benchmark target
run: ./ci/build-bench.sh
- name: Run smoke benchmark
run: ./ci/run-bench.sh --profile smoke --output bench-results.json
- name: Compare with baseline
run: ./ci/compare-bench.sh bench-results.json baseline/smoke.json
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: smoke-benchmark-results
path: bench-results.json
nightly-benchmark:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup toolchain
run: ./ci/setup-toolchain.sh
- name: Build benchmark target
run: ./ci/build-bench.sh
- name: Run full benchmark
run: ./ci/run-bench.sh --profile full --output bench-results.json
- name: Persist benchmark history
run: ./ci/persist-bench.sh bench-results.json
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: nightly-benchmark-results
path: bench-results.json
release-benchmark:
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup toolchain
run: ./ci/setup-toolchain.sh
- name: Build benchmark target
run: ./ci/build-bench.sh
- name: Run release benchmark
run: ./ci/run-bench.sh --profile release --output bench-results.json
- name: Compare with release baseline
run: ./ci/compare-bench.sh bench-results.json baseline/release.json
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: release-benchmark-results
path: bench-results.jsonPoin penting dari contoh di atas:
- Job benchmark dipisahkan berdasarkan event.
- Setup, build, run, compare, dan persist dibagi menjadi script terpisah agar mudah diulang lokal.
- Output disimpan ke file terstruktur, bukan hanya log terminal.
Contoh GitLab CI
stages:
- build
- benchmark
build_bench:
stage: build
script:
- ./ci/setup-toolchain.sh
- ./ci/build-bench.sh
artifacts:
paths:
- build/
smoke_benchmark:
stage: benchmark
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
script:
- ./ci/run-bench.sh --profile smoke --output bench-results.json
- ./ci/compare-bench.sh bench-results.json baseline/smoke.json
artifacts:
paths:
- bench-results.json
nightly_benchmark:
stage: benchmark
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
script:
- ./ci/run-bench.sh --profile full --output bench-results.json
- ./ci/persist-bench.sh bench-results.json
artifacts:
paths:
- bench-results.jsonStrukturnya sama: bedakan jenis benchmark berdasarkan sumber pipeline, dan hindari menjalankan benchmark mahal di semua jalur.
Kapan benchmark dijalankan di PR, nightly, dan release
Di PR
- Jalankan smoke benchmark untuk skenario inti.
- Gunakan threshold lebih longgar.
- Tampilkan ringkasan delta terhadap baseline di komentar PR atau summary job.
- Hindari benchmark terlalu lama yang memperlambat review.
Di nightly
- Jalankan full benchmark dengan lebih banyak iterasi.
- Simpan hasil ke histori.
- Gunakan untuk memperbarui baseline statistik.
- Deteksi tren penurunan bertahap yang tidak terlihat di PR.
Di release
- Jalankan suite benchmark yang lebih ketat dan terdokumentasi.
- Pastikan environment semirip mungkin dengan baseline release.
- Gunakan hasil untuk catatan rilis, validasi performa, atau keputusan rollback.
Anti-pattern umum
Menggagalkan PR karena noise kecil
Jika threshold terlalu agresif, tim akan mengabaikan benchmark karena terlalu banyak false alarm. Mulailah dari kebijakan konservatif, lalu ketatkan setelah melihat distribusi hasil nyata.
Mencampur hasil dari runner berbeda
Baseline dari mesin A tidak boleh dipakai untuk menilai hasil dari mesin B tanpa penyesuaian. Simpan identitas runner sebagai dimensi data.
Benchmark tanpa metadata
Angka benchmark tanpa commit, input, dan toolchain hampir tidak berguna untuk investigasi.
Menjalankan benchmark yang terlalu kecil
Untuk operasi yang sangat cepat, overhead harness, scheduler, atau timer bisa mendominasi. Solusinya adalah memperbesar batch kerja atau mengukur banyak operasi per iterasi.
Tidak ada jalur reproduksi lokal
Developer perlu bisa menjalankan command benchmark yang mirip dengan CI. Jika pipeline hanya hidup di YAML dan sulit direproduksi, investigasi regresi menjadi lambat.
Tips debugging saat hasil benchmark tiba-tiba berubah
- Bandingkan metadata: cek runner, branch, toolchain, dan input.
- Ulangi run: lihat apakah perubahan konsisten di run berikutnya.
- Jalankan benchmark lokal pada commit sebelum dan sesudah perubahan.
- Cek perubahan dependency atau opsi build yang ikut ter-merge.
- Periksa distribusi, bukan hanya mean: lonjakan variansi sering menunjukkan masalah environment.
- Lihat skenario spesifik: regresi bisa hanya muncul di ukuran input tertentu.
Checklist implementasi untuk repo kecil-menengah
- Tentukan 3-10 skenario benchmark yang benar-benar penting.
- Pisahkan profil smoke, full, dan release.
- Kunci seed, ukuran input, dan mode build.
- Simpan hasil dalam JSON atau format terstruktur lain.
- Tambahkan metadata commit, runner, toolchain, dan event CI.
- Gunakan baseline eksplisit per jenis benchmark.
- Terapkan warning threshold dan fail threshold.
- Upload artifact pada setiap run.
- Simpan histori nightly untuk analisis tren.
- Sediakan script lokal yang sama dengan command CI.
- Dokumentasikan cara membaca hasil dan kapan alarm dianggap valid.
Penutup
Membangun CI benchmark stabil untuk evaluasi struktur data cepat tidak bergantung pada satu tool tertentu, melainkan pada disiplin desain benchmark, kontrol environment, dan automation yang konsisten. Dalam konteks OpenZL, pendekatan paling efektif adalah memisahkan benchmark cepat untuk PR dari benchmark penuh untuk nightly dan release, lalu menyimpan hasil secara terstruktur agar bisa dibandingkan terhadap baseline dan divisualisasikan dari waktu ke waktu.
Jika diterapkan dengan benar, benchmark CI tidak lagi menjadi angka acak di log pipeline, tetapi berubah menjadi alat engineering yang membantu tim mendeteksi regresi performa lebih awal, mengevaluasi perubahan struktur data dengan lebih objektif, dan membuat keputusan release dengan dasar yang lebih kuat.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!