Belajar Rust - Performance Tuning & Profiling
Series/Belajar Rust/Episode 13
Episode 13 of 19

Belajar Rust - Performance Tuning & Profiling

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

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.

Profiling dengan perf dan Cargo Flamegraph

perf untuk Sampling CPU

perf adalah profiler di kernel Linux. perf stat meringkas metrik proses, perf record mengambil sampel call stack:

Statistik proses
cargo build --release
perf stat ./target/release/aplikasi

cargo 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.

Visualisasi dengan Cargo Flamegraph

Flamegraph mengubah sampel profiler menjadi peta api yang menunjukkan fungsi mana yang memakan waktu:

Membuat flamegraph
cargo install flamegraph
cargo flamegraph

cargo 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.

Tokio-Console untuk Async

Melihat Task Async

Aplikasi tokio sulit diprofil dengan perf biasa karena task berpindah antar thread. tokio-console menampilkan task, resource, dan channel secara live:

Mengaktifkan tokio-console
cargo add tokio --features full
cargo add tokio-util --features rt
Runtime dengan console
use 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 run

Jalankan dengan fitur tracing aktif dan tokio-console sebagai viewer terpisah:

Menjalankan console
cargo install tokio-console
RUSTFLAGS="--cfg tokio_unstable" cargo run
tokio-console

tokio-console menampilkan task yang idle terlalu lama, channel yang penuh, dan resource yang tersangkut. Alat ini wajib untuk mengoptimasi server async di production.

Zero-Cost Abstractions dan Iterators

Abstraksi Tanpa Overhead

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.

Menggunakan Iterators dengan Efisien

Iterator pipeline
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 --release

Pipeline filtermapsum 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.

Hindari Alokasi Tak Perlu

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.

Menangani Bottleneck I/O, Allocation, dan Lock

I/O: Batch dan Non-Blocking

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.

Allocation: Ukuran dan Perilaku

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: Kurangi Saling Tunggu

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.

Benchmark dengan Criterion

Untuk mengukur perbaikan, gunakan criterion — framework benchmark dengan analisis statistik:

Menambah criterion
cargo add --dev criterion
Benchmark sederhana
cat > 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 bench

cargo 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.

Penutup

Inti yang harus dibawa pulang:

  • Optimasi selalu dimulai dari pengukuran, bukan tebakan.
  • perf stat memberi gambaran besar; cargo flamegraph menunjukkan fungsi panas.
  • tokio-console menampilkan task async, channel, dan resource secara live.
  • Iterators dan generics adalah zero-cost; gunakan tanpa ragu.
  • Bottleneck klasik: I/O blocking, allocation berlebih, dan lock contention.
  • Criterion membuat benchmark berulang dengan baseline yang terukur.

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.

Belajar Rust - Performance Tuning & Profiling | Belajar Rust