Episode ini mengoptimasi aplikasi Rust: memprofil dengan perf, tokio-console, dan cargo flamegraph, memanfaatkan zero-cost abstractions dan iterators, serta menangani bottleneck I/O, allocation, dan lock contention dengan bantuan criterion untuk benchmark.

Rust dikenal cepat, tetapi kecepatan itu tidak datang dengan sendirinya — ia datang dari pengukuran. Episode 13 membahas profiling dan performance tuning: bagaimana menemukan di mana waktu benar-benar dihabiskan, lalu mengoptimasi bagian yang penting.
Kita mulai dari mindset: optimasi tanpa pengukuran adalah tebakan. Lalu memakai alat profiling perf, tokio-console, dan cargo flamegraph, memahami zero-cost abstraction, dan menangani tiga bottleneck klasik: I/O, allocation, dan lock contention.
perf adalah profiler di kernel Linux. perf stat meringkas metrik proses, perf record mengambil sampel call stack:
cargo build --release
perf stat ./target/release/aplikasicargo build --release membuat binary teroptimasi — profiling harus selalu memakai build release. perf stat ./target/release/aplikasi menampilkan CPU usage, cache misses, dan branch mispredictions. Mulailah dari sini untuk tahu gambaran besar.
Flamegraph mengubah sampel profiler menjadi peta api yang menunjukkan fungsi mana yang memakan waktu:
cargo install flamegraph
cargo flamegraphcargo flamegraph menjalankan binary dan menghasilkan flamegraph.svg. Panjang pita menunjukkan waktu yang dihabiskan tiap fungsi. cargo install flamegraph memasang alatnya sekali; hasil SVG bisa dibuka di browser untuk dijelajahi.
Aplikasi tokio sulit diprofil dengan perf biasa karena task berpindah antar thread. tokio-console menampilkan task, resource, dan channel secara live:
cargo add tokio --features full
cargo add tokio-util --features rtuse tokio_util::task::TaskTracker;
#[tokio::main]
async fn main() {
let tracker = TaskTracker::new();
tracker.spawn(async {
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
println!("task selesai");
});
tracker.close();
tracker.wait().await;
}
EOF
cargo runJalankan dengan fitur tracing aktif dan tokio-console sebagai viewer terpisah:
cargo install tokio-console
RUSTFLAGS="--cfg tokio_unstable" cargo run
tokio-consoletokio-console menampilkan task yang idle terlalu lama, channel yang penuh, dan resource yang tersangkut. Alat ini wajib untuk mengoptimasi server async di production.
Rust menjanjikan abstraksi yang tidak menambah biaya runtime: generics dimonomorphize saat kompilasi, dan iterators biasanya dioptimasi menjadi loop yang setara dengan kode manual. Ini berarti kalian bisa menulis kode ekspresif tanpa takut overhead.
cat > src/main.rs <<'EOF'
fn main() {
let data = (0..1_000_000u64).collect::<Vec<_>>();
let jumlah: u64 = data
.iter()
.filter(|n| n % 2 == 0)
.map(|n| n * 3)
.sum();
println!("jumlah: {}", jumlah);
}
EOF
cargo run --releasePipeline filter → map → sum dikompilasi menjadi satu loop yang efisien, bukan serangkaian alokasi perantara. cargo run --release menjalankan versi teroptimasi. Selalu gunakan iterator adapter alih-alih loop manual untuk operasi koleksi — kecuali profil menunjukkan ini benar-benar bottleneck.
String::from dan to_string mengalokasi di heap. Untuk format satu kali, format! wajar; dalam loop panas, pertimbangkan menulis ke Vec<u8> yang dipakai ulang. Pengetahuan tentang stack vs heap (episode 6) langsung terasa nilainya di sini.
I/O lambat karena menunggu, bukan menghitung. Strategi: batch operasi kecil menjadi satu operasi besar, pakai async I/O (episode 8), dan hindari memblokir runtime async dengan pekerjaan sinkron. Jika server async melambat, periksa apakah ada panggilan sinkron yang memblokir thread.
Alokasi berlebihan memicu pressure GC-free tetapi tetap mahal karena panggilan allocator dan page fault. Pantau dengan flamegraph: pita lebar di fungsi allocator menandakan masalah. Pertimbangkan struktur data seperti Vec::with_capacity yang mengalokasi sekali dengan ukuran yang diketahui.
Lock contention terjadi saat banyak task memperebutkan mutex yang sama. Gejalanya: flamegraph menunjukkan banyak waktu di lock. Solusinya berjenjang: persempit kritikal section, gunakan RwLock untuk workload read-heavy, atau ganti pola Arc<Mutex> dengan pola actor (episode 12) yang menghilangkan lock sama sekali.
Untuk mengukur perbaikan, gunakan criterion — framework benchmark dengan analisis statistik:
cargo add --dev criterioncat > benches/jumlah.rs <<'EOF'
use criterion::{criterion_group, criterion_main, Criterion};
fn jumlah_paralel(n: u64) -> u64 {
(0..n).filter(|x| x % 2 == 0).map(|x| x * 3).sum()
}
fn bench(c: &mut Criterion) {
c.bench_function("jumlah_paralel", |b| {
b.iter(|| jumlah_paralel(1_000_000))
});
}
criterion_group!(benches, bench);
criterion_main!(benches);
EOF
cargo benchcargo bench menjalankan benchmark dan menampilkan estimasi waktu per iterasi. Criterion mencatat baseline, sehingga setiap perubahan kode bisa diukur naik atau turun. cargo add --dev criterion menambahkannya sebagai dev-dependency.
Inti yang harus dibawa pulang:
perf stat memberi gambaran besar; cargo flamegraph menunjukkan fungsi panas.tokio-console menampilkan task async, channel, dan resource secara live.Di episode 14 selanjutnya kita akan membahas observability, logging, dan tracing — logging dengan tracing dan log serta ekosistem subscriber, distributed tracing dengan tracing-opentelemetry, dan metrics Prometheus lewat prometheus atau opentelemetry. Kalian akan melihat apa yang terjadi di dalam service kalian.