Mengoptimalkan kode Haskell secara ilmiah: memahami strictness analysis dan unlifted types, menghindari space leak akibat akumulasi lazy, lalu memakai profiler GHC dengan cabal build --enable-profiling dan +RTS -p, heap profile untuk menemukan kebocoran memori, serta benchmark yang andal dengan Criterion.

Sampai episode 20 kalian membangun aplikasi yang benar dan aman. Episode 21 ini menjawab pertanyaan yang selalu datang berikutnya: bagaimana membuatnya cepat — dan yang lebih penting, bagaimana mengetahuinya secara ilmiah, bukan dengan firasat.
Mengapa bab ini penting? Karena di Haskell, "optimasi dengan feeling" sering keliru arah: laziness membuat intuisi tentang biaya jadi menyesatkan. Kalian tidak bisa menebak di mana bottleneck berada — kalian harus mengukur. Episode ini mengajarkan disiplin profiling: strictness analysis untuk memahami, profiler untuk menemukan, dan Criterion untuk membuktikan perbaikan.
Di bahasa imperatif, biaya ≈ jumlah statement. Di Haskell, biaya dipengaruhi tiga hal yang tidak terlihat: alokasi (boxed vs unlifted), thunk (evaluasi tertunda), dan GC. Memori short-lived sangat murah di Haskell (GC generational), tetapi thunk yang menumpuk dan alokasi yang bocor ke generasi tua adalah biaya tersembunyi terbesar.
Compiler GHC melakukan strictness analysis: ia membuktikan parameter mana yang pasti dibutuhkan, lalu membuatnya strict tanpa mengubah semantik. Ini artinya banyak kode "lazy" di-highlight oleh compiler menjadi se-efisien versi eager — kalian sering mendapat performa tanpa mengubah apa pun.
add :: Int -> Int -> Int
add x y = x + y
-- x dan y pasti dibutuhkan -> GHC membuatnya strictCek dengan flag -ddump-stranal untuk melihat hasil analisis ini. Pesan pentingnya: optimalkan setelah mengukur, karena GHC sudah mengoptimalkan banyak hal sendiri.
Nilai Int biasa adalah boxed: pointer ke heap yang berisi nilai. Unlifted types (Int#) menyimpan nilai langsung di register — tanpa alokasi heap. Kode "hot loop" yang menuntut kecepatan maksimal bisa memakainya lewat MagicHash, tetapi harganya: kode menjadi jauh lebih berbahaya (bisa crash jika salah).
{-# LANGUAGE MagicHash #-}
import GHC.Exts
siklusCepat :: Int# -> Int#
siklusCepat 0# = 0#
siklusCepat n = n +# siklusCepat (n -# 1#)Aturan praktis: jangan mulai dari sini. Gunakan Int biasa, profil, dan hanya jika profiler membuktikan hot path-nya, pertimbangkan unlifted. Sebagian besar kode production tidak pernah membutuhkannya.
Kita sudah menyinggung space leak di episode 7. Di sini kita melihatnya dari sisi performa: space leak = thunk menumpuk = memori naik tanpa henti = GC bekerja keras = program melambat.
Pola paling umum:
-- BERBAHAYA: foldl membangun rantai thunk
jumlahLambat :: [Int] -> Int
jumlahLambat = foldl (+) 0
-- AMAN: foldl' mengevaluasi akumulator tiap langkah
import Data.List (foldl')
jumlahCepat :: [Int] -> Int
jumlahCepat = foldl' (+) 0Pola lain yang kerap bocor:
++ di kiri: acc ++ "x" di dalam loop → struktur O(n²) dan thunk. Gunakan list kanan + reverse, atau Text dengan Data.Text.Builder.length lalu !! berulang pada list yang sama — list dijelajahi ganda.Data.Map.Strict.Warning
Gejala space leak: program berjalan makin lambat seiring waktu, penggunaan memori naik monoton, dan +RTS -s menunjukkan residensi memori jauh di atas yang dibutuhkan. Jangan "memperbaiki" dengan menebak — konfirmasi dulu dengan heap profile di bawah, baru perbaiki pola yang terbukti bermasalah.
Profiling di GHC terdiri dari dua level: time profiling (di mana waktu dihabiskan) dan heap profiling (di mana memori dihabiskan).
Build dengan profiling diaktifkan:
cabal build --enable-profiling
./app +RTS -pPerintah menghasilkan file app.prof yang menampilkan cost centre — fungsi mana yang menyumbang waktu paling besar:
COST CENTRE MODULE SRC %time
main Main Main.hs:1:1 45.6
jumlahLambat Data.List 30.1+RTS -s menambahkan ringkasan runtime:
./app +RTS -sMenampilkan total alokasi, residensi maksimum, dan waktu GC — ukuran pertama sebelum masuk terlalu dalam.
Heap profile menunjukkan apa yang mendominasi memori. Profil per biografi (alokasi vs survival) bisa mengungkap thunk yang bertahan:
./app +RTS -hT
# menghasilkan app.hp, render ke grafik
hp2ps -c app.hpGrafik app.ps (atau versi PDF) menunjukkan bagaimana heap bertumbuh. Pola yang mencurigakan: penggunaan memori yang naik linier padahal data masukan konstan — tanda thunk menumpuk. Biografi SURVIVED yang tinggi menunjukkan data yang seharusnya short-lived justru dipertahankan.
Tip
Workflow profiling yang benar: (1) ukur baseline dengan +RTS -s, (2) buat heap profile untuk melihat jenis memori, (3) perbaiki satu pola yang terbukti (biasanya fold/akumulasi lazy), (4) ukur ulang dan bandingkan. Satu perubahan per iterasi — kalian harus tahu perubahan mana yang menghasilkan efek.
Profiler menjawab "di mana" dan "mengapa"; benchmark menjawab "berapa banyak" — dan mengunci agar regresi terdeteksi. Criterion adalah benchmark library standar dengan statistik yang jujur:
import Criterion.Main
slowSum :: [Int] -> Int
slowSum = foldl (+) 0
fastSum :: [Int] -> Int
fastSum = foldl' (+) 0
main :: IO ()
main = defaultMain
[ bench "foldl (lazy)" $ whnf slowSum (take 1000000 [1..])
, bench "foldl' (strict)" $ whnf fastSum (take 1000000 [1..])
]Jalankan dengan:
cabal benchCriterion menjalankan banyak iterasi, menghitung rata-rata dengan interval kepercayaan, dan menampilkan perbandingan:
benchmarking foldl' (strict)
time 9.104 ms (8.9 ms .. 9.3 ms)
0.999 R² (0.998 R² .. 1.000 R²)
mean 9.142 msPerhatikan whnf — argumen dievaluasi ke WHNF (Weak Head Normal Form), standar untuk benchmark fungsi murni. Untuk data struktur yang perlu dievaluasi penuh, gunakan nf.
Kesalahan umum benchmark Haskell:
whnf/nf dengan benar.-O2 + benchmark kosong bisa menghasilkan "0 detik"; pastikan hasil benar-benar dibutuhkan (cetak ke output yang di-blackhole).| Langkah | Alat | Kapan |
|---|---|---|
| Ukur baseline | +RTS -s | selalu sebelum optimasi |
| Temukan hot spot | +RTS -p | saat ada fungsi mencurigakan |
| Cari space leak | +RTS -hT | memori naik tanpa henti |
| Buktikan perbaikan | Criterion | sebelum & sesudah setiap perubahan |
| Awasi regresi | CI benchmark | setiap PR |
Urutan ini mencegah kesalahan terbesar di optimasi Haskell: "mengoptimalkan hal yang tidak lambat" atau "memperbaiki yang tidak rusak".
foldl untuk akumulasi — ganti foldl'; space leak paling umum.Text/ByteString untuk workload besar.String untuk parsing besar — Text jauh lebih cepat dan hemat memori.whnf — hasil tidak mencerminkan biaya nyata.Inti yang harus dibawa pulang:
Int#) untuk hot path ekstrem — gunakan hanya saat profiler membuktikan perlu.foldl', struktur data strict, Text.cabal build --enable-profiling, +RTS -p (waktu), +RTS -hT (heap).Di episode 22 — episode terakhir series ini — kita akan membahas ekosistem, alternatif & refleksi akhir — membandingkan Haskell dengan Scala, Rust, Elm, dan PureScript, kapan memilih bahasa mana, rekap keseluruhan episode 0-21, checklist production, serta sumber belajar resmi untuk melanjutkan. Sampai jumpa di episode terakhir!