🚀 Go Binary Bukan Sekadar Kode Anda: Membongkar Isi Sebenarnya dari Hasil Build Golang
Banyak developer percaya bahwa hasil build Go adalah pure binary dalam arti: hanya berisi kode yang mereka tulis, dikompilasi menjadi machine code, lalu selesai.
Secara praktis, memang benar Go menghasilkan native executable yang bisa langsung dijalankan tanpa runtime eksternal. Namun secara teknis, ada sesuatu yang sering tidak disadari:
Binary Go bukan hanya berisi kode aplikasi Anda. Di dalamnya terdapat runtime Go yang ikut ter-embed.
Artikel ini akan membahas secara rinci:
- Apa sebenarnya isi binary Go
- Apa itu runtime Go dan kenapa ia ikut ter-bundle
- Perbedaan dengan bahasa lain
- Static vs dynamic linking
- Cara menganalisis isi binary
- Implikasi terhadap ukuran, performa, dan deployment
Apa yang Dimaksud “Pure Binary”?
Istilah pure binary sering dimaknai sebagai:
- Tidak butuh interpreter
- Tidak butuh VM
- Tidak butuh runtime eksternal
- Bisa langsung dijalankan di OS target
Dalam definisi ini, Go memang memenuhi kriteria tersebut.
Namun ada perbedaan penting antara:
- Tidak butuh runtime eksternal
- Tidak memiliki runtime sama sekali
Go tidak membutuhkan runtime eksternal seperti JVM atau Node.js. Tetapi Go tetap memiliki runtime internal yang di-link ke dalam binary.
Struktur Dasar Binary Go
Mari kita lihat contoh program sederhana:
package main
import "fmt"
func main() {
fmt.Println("Halo Dunia")
}
Build:
go build -o app
Hasilnya adalah satu file executable yang dapat langsung dijalankan.
Namun di dalam file tersebut bukan hanya fungsi main() Anda.
Binary tersebut juga mengandung berbagai komponen runtime Go.
Apa Saja yang Termasuk dalam Runtime Go?
Runtime Go bukan VM. Ia adalah kumpulan subsistem yang memungkinkan fitur bahasa Go bekerja.
Beberapa komponen penting yang biasanya ikut ter-embed:
🔹 1. Garbage Collector
Go memiliki GC bawaan. Maka kode pengelolaan memori otomatis ikut ter-include.
🔹 2. Scheduler Goroutine (M:N Scheduler)
Go menjalankan goroutine dengan model M:N:
- M goroutine
- N OS thread
Scheduler ini mengatur bagaimana goroutine dipetakan ke thread OS.
🔹 3. Manajemen Stack
Stack goroutine dapat tumbuh dan menyusut secara dinamis. Logika ini berada di runtime.
🔹 4. Memory Allocator
Fungsi seperti make, new, map, slice — semua menggunakan allocator runtime.
🔹 5. Channel & Select Implementation
Operasi channel bukan fitur OS. Ia diimplementasikan oleh runtime.
🔹 6. Panic & Recover
Sistem exception-like Go ditangani oleh runtime.
🔹 7. Interface Dispatch & Reflection
Type assertion dan reflection membutuhkan metadata runtime.
Semua ini ada di dalam binary Anda.
Kenapa Runtime Ini Harus Ada?
Bandingkan dengan C.
C tidak memiliki:
- GC
- Goroutine
- Channel
- Reflection
Karena itu binary C bisa jauh lebih kecil.
Go menawarkan fitur tingkat tinggi, dan fitur tersebut memerlukan dukungan runtime.
Tanpa runtime embedded, fitur bahasa Go tidak dapat berjalan.
Static Linking vs Dynamic Linking
Secara default (tanpa cgo), Go melakukan static linking. Artinya semua dependency dikompilasi dan di-link langsung ke dalam binary.
Anda bisa memeriksa dengan:
ldd app
Jika muncul:
not a dynamic executable
Artinya binary sepenuhnya static.
Namun jika menggunakan cgo, bisa muncul dependency seperti:
libc.so.6
Artinya binary membutuhkan library eksternal saat runtime.
Untuk memaksa static build:
CGO_ENABLED=0 go build
Mengapa Binary Go Lebih Besar?
Perbandingan kasar Hello World:
- C: ~20 KB
- Rust: ~300 KB
- Go: 1–2 MB
Penyebab utama:
- Runtime embedded
- Garbage collector
- Scheduler
- Symbol table
- Debug information
Untuk mengecilkan ukuran:
go build -ldflags="-s -w"
Flag ini menghapus simbol debug dan DWARF.
Cara Melihat Isi Binary Go
Beberapa tools yang bisa digunakan:
🔎 nm
Melihat simbol dalam binary
nm app
Anda akan melihat banyak simbol dengan prefix runtime.
🔎 ldd
Melihat dependency dynamic
ldd app
🔎 size
Melihat pembagian text/data/bss section
size app
Dari sini akan terlihat bahwa kode runtime menyumbang bagian signifikan.
Perbandingan Model Eksekusi Bahasa
| Bahasa | Runtime Eksternal | Runtime Embedded | Output |
|---|---|---|---|
| Go | Tidak | Ya | Native binary |
| C | Tidak | Minimal | Native binary |
| Rust | Tidak | Minimal | Native binary |
| Java | Ya (JVM) | Tidak | Bytecode |
| Node.js | Ya | Tidak | Script |
| Python | Ya | Tidak | Bytecode |
Go berada di tengah:
- Bukan VM-based
- Tapi juga bukan minimal-runtime seperti C
Implikasi Arsitektural
Keuntungan:
- Distribusi sangat mudah
- Tidak perlu install runtime di server
- Cocok untuk container minimal (scratch)
- Cross-compilation sangat mudah
Konsekuensi:
- Ukuran binary lebih besar
- Runtime membawa overhead tertentu
- Startup time sedikit lebih kompleks dibanding C murni
Penutup
Go memang menghasilkan native binary.
Namun binary tersebut bukan hanya “kode Anda saja”. Ia adalah:
Kode Anda + Runtime Go yang mendukung seluruh fitur bahasa.
Dan itulah kekuatan sekaligus trade-off Go:
- Mudah didistribusikan
- Self-contained
- Kaya fitur
- Namun tidak minimal seperti C
Memahami ini penting agar kita tidak salah kaprah ketika mengatakan:
“Go itu pure binary tanpa runtime.”
Yang benar adalah:
“Go adalah native binary dengan runtime ter-embed.”