Episode ini mengoptimasi program C secara ilmiah: profiling dengan perf, gprof, dan Valgrind untuk menemukan titik lambat, optimasi kode dengan loop unrolling, inline, dan compiler flags, memory locality dan cache friendliness, serta tradeoff readability.

Performansi adalah alasan utama memilih C, tetapi optimasi yang salah arah justru membuang waktu dan merusak kode. Episode 15 mengajarkan optimasi secara ilmiah: ukur dulu, temukan titik lambat, lalu perbaiki bagian yang benar-benar menjadi penghambat.
Kita akan memakai tiga alat profiling: gprof untuk profil fungsi, Valgrind callgrind untuk menghitung instruksi dan cache misses, serta perf untuk sampling berbasis kernel yang ringan. Setelah titik lambat ditemukan, kita bicara optimasi: compiler flags, inline, loop unrolling, dan tata letak data yang ramah cache.
Aturan emas sepanjang episode: jangan mengoptimasi kode yang tidak terukur. Ukuran kecepatan harus dilakukan dengan alat, bukan perasaan.
gprof menghitung waktu yang dihabiskan setiap fungsi. Bangun dengan bendera -pg, jalankan program, lalu baca laporannya:
gcc -pg -O2 program.c -o program
./program
gprof ./program gmon.outBendera -pg menyuntikkan pencatatan waktu ke dalam fungsi, dan output gprof ./program gmon.out menampilkan persentase waktu per fungsi. Fungsi yang paling besar waktunya adalah kandidat utama optimasi.
Untuk detail cache dan instruksi, pakai callgrind:
valgrind --tool=callgrind ./programvalgrind --tool=callgrind mencatat setiap instruksi dan memori access. Hasilnya dianalisis dengan callgrind_annotate untuk melihat baris mana yang paling sering dieksekusi. perf stat dari sisi lain mengukur counter hardware seperti cache misses tanpa mengubah program:
perf stat ./programOutput perf stat ./program menunjukkan clock, instruksi, dan cache misses yang terjadi selama eksekusi. Kombinasikan gprof untuk melihat fungsi, dan perf atau callgrind untuk melihat baris serta akses memori.
Compiler modern sudah pandai mengoptimasi. Mulai dengan memilih level optimasi:
-O0: tanpa optimasi, tercepat saat kompilasi, untuk debugging.-O1: optimasi dasar yang aman.-O2: level default untuk produksi, menyeimbangkan kecepatan dan ukuran.-O3: optimasi agresif, bisa memperbesar binary.-march=native: memakai instruksi spesifik CPU lokal.gcc -O2 -march=native -flto program.c -o programBendera -march=native mengaktifkan instruksi yang didukung CPU kalian, dan -flto memungkinkan optimasi antar file saat linking. Perlu diingat: binary hasil -march=native tidak bisa dipindahkan ke CPU lain.
Loop unrolling menggandakan badan loop untuk mengurangi overhead pemeriksaan, dan inline menyisipkan badan fungsi ke titik panggilan. Keduanya bisa ditulis manual, tetapi compiler dengan -O2 sering melakukannya sendiri lebih baik. Tulis kode yang jelas, beri tanda inline dan restrict saat diperlukan, lalu biarkan compiler memutuskan.
CPU jauh lebih cepat daripada memori utama; cache menjadi penyangga. Program yang membaca data berurutan memanfaatkan cache line yang sama dan jauh lebih cepat daripada yang melompat acak. Inilah mengapa array C yang berurutan diakses jauh lebih cepat daripada linked list yang tersebar.
Untuk array struct, urutkan anggota dari terbesar ke terkecil untuk mengurangi padding. Akses data dengan pola yang berurutan. Saat memproses array dua dimensi, iterasi baris per baris sesuai urutan memori:
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
jumlah += matriks[i][j];
}
}Loop for (int i...) { for (int j...) } mengakses matriks[i][j] baris per baris, mengikuti urutan penyimpanan. Menukar urutan loop menjadi kolom per kolom akan membuat akses memori melompat dan menghancurkan cache locality.
Sebagian besar program tidak perlu optimasi agresif. Mulai dengan kode yang jelas dan benar, lalu ukur. Optimasi hanya bagian yang terbukti menjadi penghambat, dan pertahankan pengukuran sebelum serta sesudah agar perubahan benar-benar membawa perbaikan.
Kode yang terbaca lebih mudah dipelihara dan di-debug. Optimasi menambah kompleksitas. Panduan sederhana: tulis kode yang jelas, aktifkan -O2, ukur, dan baru optimasi bagian yang menjadi bottleneck dengan bukti angka. Simpan profil sebelum perubahan sebagai pembanding, dan pertimbangkan komentar singkat yang menjelaskan alasan trik yang tidak jelas.
Tip
Jika kecepatan benar-benar kritis, pertimbangkan membandingkan beberapa implementasi dengan benchmark yang sama. Data memilih pemenang, bukan intuisi.
Inti yang harus dibawa pulang:
Di episode 16 selanjutnya kita akan membahas concurrency dan parallel programming — multithreading dengan pthreads, synchronization primitives seperti mutex, semaphore, dan condition variable, shared memory, race conditions, dan deadlock avoidance, hingga pengenalan OpenMP untuk parallel loops.