Kode yang sama dapat dikompilasi dengan dua kepribadian berbeda. Saat mengembangkan, kita ingin build cepat dan galat informatif — pesan error, nomor baris, dan kemampuan debugger lebih berharga daripada kecepatan eksekusi. Saat merilis, keinginan kita terbalik: program harus secepat dan sekecil mungkin, dan informasi debug boleh dibuang. Cargo menyediakan mekanisme profile untuk dua suasana tersebut — dev dan release — lengkap dengan nilai bawaan yang masuk akal serta tabel [profile.*] di manifest untuk menyesuaikannya. Chapter ini mengukur dampak nyata kedua profil pada satu program penghitung bilangan prima, lalu mengatur kunci opt-level, debug, lto, dan strip untuk memeras ukuran dan kecepatan sesuai kebutuhan.

Dua Tujuan, Dua Profil #

Perintah cargo build memakai profil dev, sedangkan cargo build --release memakai profil release. Profil mengendalikan code generation yang diteruskan cargo ke rustc: tingkat optimasi, jumlah informasi debug, pengecekan overflow, sampai tanda linker. Status akhir build menampilkan profil yang dipakai — dan kebetulan merangkum dua kepribadian tersebut:

text
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.20s
text
    Finished `release` profile [optimized] target(s) in 0.08s

Frasa [unoptimized + debuginfo] menyatakan profil dev: optimasi dimatikan, informasi debug dinyalakan. Frasa [optimized] menyatakan lawannya. Nilai bawaan kedua profil adalah sebagai berikut:

Kuncidevrelease
opt-level03
debugtruefalse
ltofalsefalse
strip"none""none"

Artifak kedua profil juga terpisah lokasi: profil dev menulis ke target/debug/, profil release menulis ke target/release/, sehingga keduanya bisa berdampingan tanpa saling menimpa.

Program Percobaan: Menghitung Prima #

Untuk merasakan bedanya, kita perlu program yang benar-benar bekerja berat. Program berikut menghitung banyaknya bilangan prima di bawah 100.000 dengan pembagian percobaan murni:

rust
fn jumlah_prima(batas: u64) -> u64 {
    let mut jumlah: u64 = 0;
    for n in 2..batas {
        let mut prima = true;
        for d in 2..n {
            if n % d == 0 {
                prima = false;
                break;
            }
        }
        if prima {
            jumlah += 1;
        }
    }
    jumlah
}

fn main() {
    let hasil = jumlah_prima(100_000);
    println!("Banyak bilangan prima di bawah 100000: {}", hasil);
}

Bedah kode di atas:

  1. Baris 1 (fn jumlah_prima(batas: u64) -> u64): fungsi penghitung menerima batas atas dan mengembalikan banyaknya prima di bawah batas.
  2. Baris 3-14 (for n in 2..batas {): loop dua tingkat — untuk tiap kandidat n, loop dalam menguji pembagi d mulai 2; sisa bagi nol berarti n bukan prima dan break menghentikan pengujuan.
  3. Baris 11-13 (if prima {): kandidat tanpa pembagi dihitung sebagai prima.
  4. Baris 20 (println!): hasil dicetak agar kita bisa memastikan kedua profil menghasilkan jawaban yang sama — kebenaran hasil tidak boleh bergantung profil.

Algoritma pembagian percobaan sengaja dipilih karena lambat — justru itulah bahan ukur yang baik.

Mengukur dev vs release #

Kita bangun dua profil lalu menyertakan masing-masing binary:

bash
cargo build
time target/debug/prima
cargo build --release
time target/release/prima

Output yang Dihasilkan:

text
Banyak bilangan prima di bawah 100000: 9592

real 0m2.478s
user 0m2.477s
sys  0m0.000s
text
Banyak bilangan prima di bawah 100000: 9592

real 0m0.766s
user 0m0.764s
sys  0m0.002s

Hasilnya konsisten antar percobaan — sekitar 2,5 sampai 2,7 detik untuk dev, sekitar 0,77 detik untuk release — artinya binary release menjalankan program lebih dari tiga kali lebih cepat dari binary dev. Ukuran berkasnya juga terpaut jauh: binary dev sebesar 4.554.736 byte, binary release sebesar 444.864 byte.

Dua angka itu punya dua penjelasan berbeda. Optimasi opt-level = 3 merapikan mesin kode — rustc menggabungkan operasi, menghapus pemeriksaan berlebih, dan memilih instruksi tercepat sehingga eksekusi memercepat. Sementara itu debug = false membuang informasi pemetaan binary-ke-sumber; buktinya, saat kita mengaktifkan kembali debug = true pada profil release, binary membengkak menjadi 4.542.600 byte meski mesin kodenya tetap teroptimasi.

Informasi debug bukan sampah — pemetaan itulah yang membuat galat runtime mencetak nomor baris pada backtrace dan membuat debugger bisa berhenti di src/main.rs baris yang tepat. Selama masa pengembangan, harga 4,5 juta byte di folder target adalah pembelian yang layak; barulah saat binary dikirim ke pemakai, informasi tersebut tidak lagi punya alasan ikut serta.

Pengecekan Overflow Ikut Berbeda #

Selain kecepatan, kedua profil berbeda pada satu perilaku yang mengejutkan pemula: pengecekan arithmetic overflow aktif di dev dan nonaktif di release. Program berikut menjumlahkan u8 sampai melampaui 255:

rust
fn main() {
    let n: u8 = std::env::var("N").map(|v| v.len()).unwrap_or(200) as u8;
    let hasil = n + 100u8;
    println!("Hasil: {}", hasil);
}

Dengan variable environment N berisi 200 karakter, n bernilai 200, lalu 200 + 100 melampaui kapasitas u8. Hasil di kedua profil:

text
attempt to add with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
text
Hasil: 44

Profil dev menghentikan program dengan panic attempt to add with overflow — pemeriksaan batas angka dinyalakan bawaan sehingga galat logika tertangkap cepat. Profil release membiarkan penjumlahan membungkus mengikuti aritmetika bilangan dua-komplemen: 300 melampaui 255 dan tersisa 44. Angka pembungkus tersebut bukan galat kompilator; angka tersebut adalah perilaku yang dipilih profil agar pemeriksaan tidak memperlambat binary produksi. Konsekuensinya nyata untuk pengujian: test yang menyangkut batas angka sebaiknya dijalankan di kedua profil bila program akan dirilis tanpa pemeriksaan.

Menyesuaikan Profil Release #

Tabel [profile.release] di manifest menimpa nilai bawaan. Konfigurasi berikut mengejar binary ramping:

toml
[profile.release]
opt-level = 3
lto = true
strip = true

Bedah ketiga kuncinya:

  1. Kunci opt-level = 3: tingkat optimasi paling agresif. Nilai lain yang umum: 0 tanpa optimasi, 1 dan 2 optimasi bertingkat, "s" dan "z" optimasi untuk memperkecil ukuran kode.
  2. Kunci lto = true: Link Time Optimization mengizinkan optimizer bekerja melintasi batas crate — seluruh kode milik kita dan dependency di-link sebagai satu kesatuan sehingga panggilan antar-crate ikut dirapikan. Waktu build release bertambah sebagai biayanya.
  3. Kunci strip = true: memerintahkan linker membuang simbol dan informasi debug dari binary akhir.

Setelah cargo build --release, ukuran binary turun menjadi 291.168 byte — sekitar sepertiga ukuran release polos. Durasi program pembilang prima kita berubah tipis menjadi 0,89 detik; LTO paling terasa pada proyek besar dengan banyak panggilan lintas-crate, bukan pada program satu berkas seperti ini.

Satu batasan penempatan perlu dicatat: tabel [profile.*] hanya sah di manifest akar — pada package tunggal akarnya adalah Cargo.toml proyek, dan pada workspace akarnya adalah manifest virtual yang kita kenal di Chapter Workspace. Cargo menolak tabel profil di manifest anggota karena satu workspace hanya boleh punya satu definisi tiap profil; kegandaan akan membuat dua anggota saling menimpa aturan build.

Menyesuaikan Profil dev #

Profil dev juga bisa disetel. Nilai opt-level = 0 kadang terasa menyiksa saat program kita sendiri berat, sementara proses debug tetap butuh informasi debug:

toml
[profile.dev]
opt-level = 1

Percobaan nyata pada program pembilang prima memendahkan durasi dev dari sekitar 2,5 detik menjadi 1,42 detik, dan status build berubah menjadi [optimized + debuginfo] — optimasi ringan aktif bersamaan dengan informasi debug yang utuh. Kompromi semacam ini cocok untuk program simulasi atau permainan yang harus tetap nyaman dijalankan selama masa pengembangan.

Ringkasan #

Profil memisahkan dua suasana build: dev mengutamakan kecepatan kompilasi dan informasi debug, release mengutamakan kecepatan eksekusi dan ukuran kecil — terukur nyata pada 2,5 detik lawan 0,77 detik serta 4,5 juta byte lawan 445 ribu byte. Tabel [profile.release] menyetel opt-level, lto, dan strip untuk memeras ukuran hingga 291 ribu byte, sedangkan [profile.dev] menerima opt-level = 1 agar iterasi tetap cepat tanpa kehilangan debug info. Ketika satu proyek tumbuh menjadi banyak crate yang ingin berbagi satu target build dan satu kunci versi, tibalah waktunya menyusun workspace — topik Chapter berikutnya.