1 poin oleh GN⁺ 2025-06-08 | 1 komentar | Bagikan ke WhatsApp
  • Optimasi tingkat rendah dapat diterapkan dengan mudah dalam bahasa Zig
  • Compiler melakukan optimasi dengan baik dalam sebagian besar situasi, tetapi terkadang kinerja yang lebih baik bisa diperoleh jika niat programmer disampaikan dengan jelas
  • Zig mendukung pembuatan kode berperforma tinggi dan metaprogramming yang kuat melalui fitur eksekusi saat kompilasi (comptime)
  • Dibandingkan dengan Rust, Zig memungkinkan optimasi yang lebih presisi melalui anotasi dan struktur kode yang eksplisit
  • Dalam operasi berulang seperti perbandingan string, comptime dapat dimanfaatkan untuk menghasilkan kode assembly yang lebih unggul daripada fungsi biasa

Optimasi dan Zig

Seperti peringatan terkenal, "Segala sesuatu mungkin dilakukan, tetapi hal yang menarik tidak bisa didapat dengan mudah," optimasi program selalu menjadi perhatian utama para pengembang. Untuk biaya infrastruktur cloud, perbaikan latensi, penyederhanaan sistem, dan sebagainya, optimasi kode mutlak diperlukan. Artikel ini berfokus menjelaskan konsep optimasi tingkat rendah di Zig serta keunggulan Zig.

Bisakah compiler dipercaya?

  • Secara umum ada banyak saran untuk "percayalah pada compiler", tetapi dalam praktiknya ada kasus ketika compiler bekerja berbeda dari harapan atau melanggar spesifikasi bahasa
  • Bahasa tingkat tinggi memiliki keterbatasan performa karena sulit menyampaikan niat (intent) dengan jelas
  • Karena eksplisitas kodenya, bahasa tingkat rendah memungkinkan compiler mengetahui informasi yang dibutuhkan untuk optimasi; misalnya, jika membandingkan fungsi maxArray di JavaScript dan Zig, Zig menyampaikan tipe yang jelas, alignment, ada tidaknya alias, dan sebagainya pada waktu kompilasi, bukan saat runtime
  • Jika operasi maxArray yang sama ditulis dalam Zig dan Rust, hasilnya hampir sama berupa kode assembly berperforma tinggi, tetapi semakin baik niat diekspresikan, semakin baik pula hasil optimasinya
  • Namun kita tidak bisa selalu percaya pada performa compiler, jadi pada bagian bottleneck kita perlu memeriksa langsung kode dan hasil kompilasinya serta mencari cara optimasi yang tepat

Peran Zig

  • Zig dapat menghasilkan kode teroptimasi tanpa informasi abstrak berkat karakteristik seperti eksplisitas yang akurat, fungsi bawaan yang kaya, pointer dan anotasi, comptime, serta Illegal Behavior yang terdefinisi dengan baik
  • Rust, berkat memory model-nya, secara default menjamin tidak ada alias pada argumen, tetapi di Zig diperlukan anotasi seperti noalias secara langsung
  • Jika hanya berdasarkan LLVM IR, tingkat optimasi Zig juga tinggi
  • Yang terpenting, comptime (eksekusi saat kompilasi) di Zig adalah alat optimasi yang sangat kuat

Apa itu comptime?

  • comptime di Zig digunakan untuk pembuatan kode, penyematan nilai konstan, pembuatan struct generik berbasis tipe, dan sebagainya, serta berperan penting dalam meningkatkan performa runtime
  • Metaprogramming dapat diimplementasikan dengan comptime
  • Berbeda dari makro di C/C++ atau sistem macro di Rust, comptime adalah kode biasa, bukan sintaks terpisah
  • Kode comptime tidak mengubah AST secara langsung, tetapi dapat memeriksa, merefleksikan, dan menghasilkan sesuatu pada waktu kompilasi untuk semua tipe
  • Fleksibilitas comptime juga memengaruhi perbaikan bahasa lain seperti Rust, dan terintegrasi secara alami ke dalam bahasa Zig

Batasan comptime

  • Beberapa fitur macro seperti token-pasting tidak dapat digantikan oleh comptime Zig
  • Karena Zig menekankan keterbacaan kode, hal seperti membuat variabel di luar scope atau mendefinisikan makro tidak diizinkan
  • Sebagai gantinya, Zig comptime memiliki banyak contoh penggunaan metaprogramming yang luas seperti refleksi tipe, implementasi DSL, dan optimasi parsing string

Optimasi perbandingan string dengan comptime

  • Fungsi perbandingan string umum dapat diimplementasikan di semua bahasa, tetapi di Zig, ketika salah satu dari dua string adalah konstanta yang diketahui saat comptime, kode assembly yang lebih efisien dapat dihasilkan
  • Misalnya, jika satu string selalu bernilai "Hello!\n", optimasi dapat dilakukan dengan membandingkan dalam blok yang lebih besar, bukan per byte
  • Untuk itu, dengan menggunakan comptime, kode berperforma tinggi seperti vektor SIMD, pemrosesan blok, dan optimasi byte sisa dapat dihasilkan pada waktu kompilasi
  • Melalui pendekatan ini, bukan hanya perbandingan string berulang, tetapi juga berbagai implementasi berorientasi performa seperti mapping berbasis data statis, perfect hash table, parser AST, dan lain-lain dapat dibuat

Kesimpulan

  • Zig sangat cocok untuk optimasi tingkat rendah, dan berkat struktur kode yang eksplisit serta fitur comptime yang kuat, performa terbaik dapat diimplementasikan secara langsung
  • Dibandingkan dengan bahasa lain seperti Rust, kemampuan pemrograman saat kompilasi dan eksplisitas Zig menjadi keunggulan besar dalam pengembangan perangkat lunak berperforma tinggi
  • Kemampuan optimasi Zig akan terus menjadi daya saing yang semakin penting ke depannya

1 komentar

 
GN⁺ 2025-06-08
Komentar Hacker News
  • Bagian yang paling menarik dari zig menurut saya adalah kesederhanaan sistem build-nya, cross-compilation, dan fokusnya pada kecepatan iterasi yang tinggi. Saya seorang pengembang game, jadi performa itu penting, tetapi untuk sebagian besar kebutuhan, kebanyakan bahasa sudah memberikan performa yang cukup. Jadi itu bukan kriteria utama dalam memilih bahasa. Dengan bahasa apa pun kita bisa menulis kode yang solid, tetapi saya mengincar framework yang future-proof dan bisa dirawat selama puluhan tahun. C/C++ menjadi pilihan default karena didukung di mana-mana, tetapi saya merasa zig juga bisa menyusul sampai ke titik itu
    • Saya iseng mencoba menjalankan zig di perangkat Kindle yang sangat tua (Linux 4.1.15), dan saya sangat terkesan dengan tingkat kematangan zig. Sebagian besar langsung berjalan, dan bahkan dengan GDB lama saya masih bisa men-debug bug aneh. Saya juga jadi terpikat pada zig. Pengalaman lengkapnya bisa dilihat di sini
    • Saya merasa kita bisa menulis kode yang kuat dengan sebagian besar bahasa, tetapi saya ingin kode modular yang bisa bertahan untuk jangka puluhan tahun. Saya suka Zig, tetapi menurut saya ia punya kekurangan dari sisi pemeliharaan jangka panjang dan modularitas. Zig adalah bahasa yang memusuhi enkapsulasi. Tidak mungkin menjadikan anggota struct sebagai private. Komentar issue ini adalah contohnya. Posisi Zig adalah seharusnya tidak ada yang namanya representasi internal yang terpisah, dan semua pengguna harus diberi tahu/didokumentasikan tentang implementasi internal. Namun, untuk menjaga kontrak API, yaitu inti dari perangkat lunak modular, kita harus bisa menyembunyikan implementasi internal, dan itu tidak memungkinkan. Semoga suatu hari Zig mendukung private field
    • Saya sempat memakai Rust secara ringan dan saya menyukainya. Tetapi saya mendengar banyak yang bilang itu "buruk", jadi sempat berhenti lalu sekarang memakainya lagi. Saya masih suka. Saya tidak terlalu paham kenapa orang begitu membencinya. Sintaks generics yang jelek juga ada di C# dan Typescript. Borrow checker pun mudah dipahami kalau sudah punya pengalaman dengan bahasa level rendah
    • Zig terasa seperti Rust yang lebih sederhana, dan Go yang lebih baik. Di sisi lain, dari alat yang dibangun di atas zig, saya benar-benar sangat kagum dengan 'bun'. bun membuat hidup jauh lebih mudah. 'uv' yang berbasis Rust juga memberi pengalaman serupa
    • Saya setuju bahwa C/C++ adalah default. Setiap kali orang mencoba membuat sesuatu yang lebih baik dari C, kebanyakan akhirnya tetap menjadi C++. Meski begitu, kita tidak boleh berhenti mencoba. Rust dan Zig adalah bukti bahwa masih ada alasan untuk berharap pada sesuatu yang lebih baik. Saya berencana belajar C++ lebih dalam mulai sekarang
  • Walaupun compiler mutakhir kadang memang melanggar spesifikasi bahasa, asumsi Clang bahwa loop tak hingga akan berakhir itu benar menurut standar sejak C11. Di C11 tertulis seperti ini: "compiler boleh mengasumsikan bahwa sebuah loop akan berakhir jika ekspresi pengendalinya bukan constant expression dan loop itu tidak melakukan operasi I/O/volatile/sync/atomic"
    • Di C++ (setidaknya hingga sebelum C++26), aturan itu berlaku untuk semua loop, tetapi seperti yang Anda katakan, di C aturan itu hanya berlaku untuk "loop yang ekspresi pengendalinya bukan constant expression". Artinya, loop tak hingga yang jelas seperti for(;;); memang harus benar-benar menjadi loop tak hingga, dan loop {} di Rust juga seharusnya begitu. Namun para pengembang LLVM kadang tampak lupa bahwa mereka bukan hanya membuat compiler C++, sehingga ketika Rust berkata "tolong buat loop tak hingga", LLVM malah menerapkan "menurut C++ hal seperti itu tidak terjadi, jadi optimalkan saja!" dan timbullah masalah. Jadinya optimisasi yang salah diterapkan pada bahasa yang salah
  • Walaupun tidak ada fitur compile-time (comptime), melakukan inline dan unroll pada perbandingan string tetap sangat mungkin di C. Contoh terkait
    • Poin itu benar! Contoh awalnya terlalu sederhana. Contoh yang lebih baik adalah compile-time suffix automaton. Selain itu, kode godbolt yang ditautkan di atas justru menunjukkan salah satu dari dua hal yang sebaiknya tidak dilakukan
  • Menurut saya, bagian yang mengatakan bytecode yang dihasilkan V8 untuk contoh JavaScript itu tidak efisien bukanlah perbandingan yang baik. Untuk Zig dan Rust, dipaksa kompilasi dengan target lingkungan yang sangat modern, sedangkan untuk V8 opsi optimisasi seperti itu tidak dipaksakan. Padahal JIT modern juga bisa melakukan vektorisasi bila kondisinya memungkinkan. Dan kebanyakan bahasa modern juga menangani optimisasi terkait string dengan cara yang mirip. Sebagai referensi, ada juga contoh C++
    • Pada dasarnya membandingkan JS dan Zig itu seperti membandingkan apel dengan salad buah. Contoh Zig memakai array dengan tipe dan ukuran tetap, sedangkan JS adalah kode 'generic' yang menerima berbagai tipe saat runtime. Karena itu, jika informasi tipe diberikan dengan baik, JIT di JS bisa menghasilkan loop yang jauh lebih cepat, walaupun mungkin tidak sampai tervectorisasi. Dalam praktiknya, TypedArray tidak terlalu sering dipakai karena biaya inisialisasinya besar, dan baru layak kalau sering dipakai ulang. Lalu artikel itu bilang kode JS menggelembung, tetapi penyebab besarnya sering kali karena JIT tidak bisa mempercayai pengecekan panjang array sehingga menambahkan guard; padahal biasanya orang menulis loop seperti i < x.length, yang justru mudah dioptimalkan JIT. Jadi memang agak nitpick, meskipun tetap ada perbedaan kecil
    • Contoh godbolt Rust dan Zig juga bisa diubah targetnya ke CPU yang lebih tua. Saya tidak terpikir soal keterbatasan target di sisi JS. Dan contoh C++ itu menunjukkan betapa bagusnya kode yang dihasilkan clang. Meski begitu, assembly-nya sendiri saat ini tetap tidak terlalu memuaskan (bahkan dengan mempertimbangkan bahwa zig dibangun untuk target CPU tertentu). Kalau ada contoh porting C++ dari compile-time Suffix Automaton, itu akan sangat menarik. Itu contoh pemakaian comptime yang nyata dan sulit diprediksi oleh compiler C++
  • Saya ragu dengan pernyataan bahwa "bahasa level tinggi kekurangan 'intent' yang dimiliki bahasa level rendah". Menurut saya justru kelebihan bahasa level tinggi adalah kemampuannya mengekspresikan intent secara jauh lebih kaya dan rinci
    • Saya juga setuju. Pada dasarnya, perbedaan bahasa level tinggi dan bahasa level rendah adalah bahwa bahasa level tinggi mengekspresikan intent, sedangkan bahasa level rendah harus menampakkan mekanisme implementasinya itu sendiri
    • Yang dimaksud 'intent' di sini bukan intent bisnis seperti "menghitung pajak pembelian ini", melainkan intent yang lebih dekat ke "apa yang harus dilakukan komputer", misalnya "geser byte ini tiga bit ke kiri". Misalnya, kode seperti purchase.calculate_tax().await.map_err(|e| TaxCalculationError { source: e })?; memang penuh dengan intent, tetapi mustahil diprediksi seperti apa machine code yang akan dihasilkannya
  • Saya sangat suka model allocator di Zig. Andai di Go kita bisa memakai sesuatu seperti allocator per-request alih-alih GC
    • Di Go, custom allocator dan arena bukan mustahil, tetapi usability-nya sangat buruk dan sulit dipakai dengan benar. Tidak ada cara di level bahasa untuk mengekspresikan atau menegakkan aturan ownership. Pada akhirnya jadinya seperti menulis C dengan sintaks yang sedikit berbeda, dan tanpa GC malah bisa lebih berbahaya daripada C++
  • Saya paham dengan komentar "saya suka verbosity Zig", tetapi jujur terdengar agak aneh. C itu longgar di sana-sini, sedangkan Zig di sisi lain sering meminta terlalu banyak 'annotation noise' (terutama untuk explicit integer casting dalam ekspresi matematika). Lihat tulisan ini. Dari sisi performa, kasus zig lebih cepat daripada c biasanya lebih karena Zig memakai pengaturan optimisasi LLVM yang lebih agresif (-march=native, whole-program optimization, dll.). Sebenarnya di C pun petunjuk optimisasi seperti unreachable bisa dilakukan lewat ekstensi bahasa, dan Clang juga sangat agresif dalam constant folding. Jadi, perbedaan antara comptime di Zig dan codegen C sering kali berasal dari pengaturan optimisasi compiler. TL;DR: kalau C terasa lambat, cek dulu pengaturan compiler-nya. Bagaimanapun, inti optimisasinya tetap LLVM
    • Untuk contoh soal casting, justru kita bisa membuat satu fungsi pembungkus agar kode lebih bisa dipakai ulang dan intent-nya lebih jelas
      fn signExtendCast(comptime T: type, x: anytype) T {
        const ST = std.meta.Int(.signed, @bitSizeOf(T));
        const SX = std.meta.Int(.signed, @bitSizeOf(@TypeOf(x)));
        return @bitCast(@as(ST, @as(SX, @bitCast(x))));
      }
      export fn addi8(addr: u16, offset: u8) u16 {
        return addr +% signExtendCast(u16, offset);
      }
      
      Cara ini juga menghasilkan assembly yang sama, dan lebih serbaguna serta jelas
    • Ide-ide Zig menarik, dan ternyata penekanannya lebih besar pada compile-time (comptime) serta whole-program compilation daripada yang saya perkirakan dari artikel aslinya. Saya setuju soal itu. Sebagai catatan, Virgil sudah mendukung pemanfaatan seluruh bahasa saat compile-time dan whole-program compilation sejak 2006. Virgil tidak menargetkan LLVM, jadi perbandingan kecepatannya pada akhirnya adalah perbandingan backend. Berkat pendekatan ini, Virgil bisa melakukan optimisasi yang sangat kuat seperti devirtualize pemanggilan method secara statis sejak awal, menghapus field/objek yang tidak dipakai semaksimal mungkin, melakukan constant propagation hingga ke field-heap object, dan spesialisasi secara sempurna
    • Ke depannya, dengan mempertimbangkan pemanfaatan AI, tampaknya bahasa yang semakin eksplisit dan verbose akan menjadi arus utama. Terlepas dari apakah orang menulis kode dengan AI atau apakah itu benar, makin banyak pengembang yang akan memilih bantuan AI, dan bahasa pun akan berubah menyesuaikannya
    • Jika backend x86 baru diperkenalkan, mungkin ke depan kita akan melihat kasus di mana perbedaan performa C dan Zig memang berasal dari proyek Zig itu sendiri
    • Soal explicit integer casting, sebentar lagi akan ada perbaikan yang membuatnya lebih rapi. Lihat diskusi terkait
  • Benchmark seperti "C lebih cepat dari Python" sebetulnya kurang tepat kalau dibingkai sebagai perbandingan bahasa itu sendiri, tetapi sebagian fitur bahasa memang bisa menjadi hambatan besar bagi optimisasi. Dengan memilih bahasa yang tepat, pengembang dan compiler sama-sama bisa mengekspresikan intent secara alami dan cepat
  • Sintaks for loop Zig terasa terlalu berantakan bagi saya. Harus menaruh dua daftar berdampingan lalu menyelaraskan posisinya; melihatnya saja sudah bikin mata lelah. Menurut saya, bahasa-bahasa belakangan ini keliru karena menuangkan terlalu banyak sintaks 'ajaib' dan simbol khusus. Rasanya sulit menatapnya berjam-jam
    • Pola melintasi dua array seperti ini sangat umum dalam kode level rendah, begitu juga iterasi paralel. Karena itu, menurut saya wajar justru jika Zig mendukungnya dengan cara yang jelas dan alami. Saya penasaran kenapa itu terasa melelahkan di mata
  • Optimisasi itu sangat penting. Efeknya justru makin besar seiring waktu
    • Namun itu hanya benar jika perangkat lunaknya benar-benar dipakai