1 puan yazan GN⁺ 2025-06-08 | 1 yorum | WhatsApp'ta paylaş
  • Düşük seviyeli optimizasyon, derleyicinin kodun niyetini ve kısıtlarını daha iyi anlamasını sağlamaya yönelik bir iştir; Zig de tür, hizalama, alias ve derleme zamanı bilgilerini açıkça ifade etmeyi kolaylaştırdığı için bu amaca iyi uyar
  • LLVM gibi optimize edici derleyiciler bile her zaman en iyi kodu üretmez; bu yüzden darboğaz bölgelerde üretilen kodu kontrol etmek ve kodu ayarlamak hâlâ gerekir
  • Zig, noalias, align, sabit dizi boyutu ve eleman türünü derleme zamanında geçirerek JavaScript örneğine göre daha küçük vektörleştirilmiş kod üretebilir
  • comptime, normal Zig kodunu derleme zamanında çalıştırarak sabit üretimi, generic uygulama, tür yansıtma ve string karşılaştırma optimizasyonu gibi metaprogramlama olanakları sağlar
  • Zig’in gücü, AST’yi doğrudan değiştiren makrolardan ziyade dile entegre derleme zamanı yürütmesindedir; bazı çalışma zamanı değerleri de derleme zamanı özelleştirme fonksiyonlarına dispatch edilebilir

Derleyiciye güvenip yine de doğrulamak neden gerekir

  • Optimizasyon yalnızca hızlı programlar üretme tekniği olmanın ötesinde maliyet azaltma, ölçeklenebilirliği artırma ve sistem sadeliğini korumayla bağlantılıdır
  • Modern derleyiciler LLVM gibi backend’lerle etkileyici sonuçlar üretir, ancak bazı durumlarda hâlâ alt düzeyde optimize edilmiş kod oluşturur
  • Düşük seviyeli dillerin hızlı olmasının nedeni yalnızca garbage collection veya interpreter overhead’inin az olması değil, derleyicinin anlayabileceği niyet bilgisini daha fazla ifade edebilmeleridir
  • Derleyici algoritmanın ya da programlama paradigmasının kendisini değiştiremez; çoğunlukla döngüler gibi sınırlı kapsamlar içinde optimizasyon yapar

JavaScript ve Zig’de dizi maksimumu örneği

  • JavaScript örneği, iki dizinin eleman bazında maksimumunu x[i] = y[i] > x[i] ? y[i] : x[i] biçiminde x içine yazar
  • İnsan için açık bir kod olsa da V8’in ürettiği bytecode şişkindir
  • Zig örneği, fonksiyon argümanlarında optimizasyon için gereken bilgileri daha somut biçimde belirtir
    • noalias x: x başka pointer’larla alias değildir
    • *align(64): 64 bayt hizalama
    • [65536]f64: dizi boyutu ve eleman türü
    • const: salt okunur argüman
  • Bu bilgiler sayesinde derleyici daha iyi kod üretebilir; örnekte vektörleştirilmiş assembly oluşturulur
  • Eşdeğer Rust kodu da neredeyse aynı assembly’yi üretir

Zig’in optimizasyon açısından avantajlı noktaları ve sınırları

  • Zig, ayrıntılı ifadeye izin vererek LLVM’e kod hakkında çok fazla bilgi aktarabilir
  • Optimizasyonla ilgili olarak Zig’in sunduğu başlıca unsurlar şunlardır
  • Rust’ın bellek modeli, derleyicinin fonksiyon argümanlarının alias oluşturmadığını her zaman varsayabilmesini sağlar; Zig’de ise bunu doğrudan belirtmek gerekir
  • Derleyici Zig fonksiyon argümanlarının alias olmadığını bilemezse, açıklama eklenmemiş bir Zig fonksiyonu Rust fonksiyonundan daha yavaş olabilir
  • Yalnızca iyi açıklama eklenmiş LLVM IR temel alınsa bile Zig iyi sonuçlar üretir; ancak daha büyük gücü derleme zamanı yürütmesindedir

comptimeın rolü

  • Zig’in comptimeı, derleme zamanında kod üretimi için bir özelliktir
  • Derleme zamanında yapılabilecek işler şunlardır
    • Sabitler üretip binary’ye dahil etmek
    • Aynı hashmap yapısını birden çok veri türü için tekrar tekrar yazmamak
    • Derleme zamanında bilinen verilere dayanarak gereksiz kodu kaldıracak optimizasyonu teşvik etmek
    • Türleri inceleyip yansıtarak ve üreterek generic uygulamak
  • comptime kodu, derleme zamanında çalıştırılan normal Zig kodudur; ağ IO’su gibi yan etkileri olamaz
  • Derleme zamanı emülasyon makinesi, derleme hedefiyle eşleşir
  • Neredeyse tüm Zig kodları comptime ile derleme zamanında çalıştırılabilir ve tüm türler derleme zamanında incelenebilir, yansıtılabilir ve üretilebilir

Makrolardan farkı

  • comptimeın amacı makrolara benzer, ancak çalışma biçimi farklıdır
  • Bazı makrolar ham metni değiştirir, bazıları programın AST’sini doğrudan değiştirir
  • Zig’in comptimeı AST’yi doğrudan değiştirmez; token birleştirme makroları gibi özellikleri de yoktur
  • Zig okunması kolay bir dil olmayı hedeflediğinden, ilgisiz scope’larda değişken oluşturan veya değiştiren makro tarzıyla uyumlu değildir
  • Makroların yapabildiği ancak Zig comptimeın doğrudan yapamadığı işler şunlardır
    • Başka makrolar tanımlamak
    • AST değiştirmek
    • Mini dil veya DSL’i doğrudan uygulamak
  • Bununla birlikte Zig’de de DSL oluşturulabilir; Zig’in print fonksiyonu, veriyi serileştirecek fonksiyon grafiğini oluşturmak için comptime ile format string’ini parse eder
  • Örnekler arasında TigerBeetle account testing DSL, comath, zilliam vardır

comptime string karşılaştırma optimizasyonu

  • Tipik string karşılaştırması, uzunluklar farklıysa false döndürür; aynıysa her baytı sırayla karşılaştırır
  • Bu yöntem iki string’in her birinden bayt okuyup karşılaştırmayı gerektirir
  • Bir string’in derleme zamanında zaten bilindiği sık görülür; bu yüzden Zig’de bir argümanın comptime olmasını isteyebilirsiniz
    • fn staticEql(comptime a: []const u8, b: []const u8) bool
  • "Hello!\n" gibi statik bir string ile karşılaştırırken derleyici, uzunluk karşılaştırması ve her baytın sabit karşılaştırmasından oluşan kod üretir
  • Bu bölümün amacı yalnızca derleyicinin otomatik yapabileceği optimizasyonları göstermek değil, comptime ile dönüşümü zorlayarak derleyicinin göremediği fırsatları açabileceğini göstermektir

Daha büyük birimlerle karşılaştırma ve SIMD kullanımı

  • Basit comptime string karşılaştırması hâlâ bayt bazında karşılaştırma yapar
  • İyileştirilmiş sürüm, karşılaştırma blok boyutunu belirlemek için std.simd.suggestVectorLength(u8) veya @sizeOf(usize) kullanır
  • Önce string uzunluğu kontrol edilir, ardından karşılaştırılabilecek büyük blok sayısı ve kalan bayt sayısı hesaplanır
  • Her blok, std.meta.Int(.unsigned, block_len * 8) ile oluşturulan tamsayı türüne @bitCast edilerek karşılaştırılır
  • Kalan baytlar da ayrı bir tamsayı türüyle karşılaştırılır
  • "Hello, World!\n" örneğinin üretilen assembly’si daha büyük register’lar kullanır ve koşullu dallanma sayısını azaltır
  • Daha büyük string karşılaştırmalarında daha büyük SIMD register’ları kullanan assembly üretilir

Çalışma zamanı değerleriyle derleme zamanı özelleştirmesini birlikte kullanmak

  • Zig’in comptimeı yalnızca derleme zamanında bilinen verilerle sınırlı değildir
  • Basit durumlarda birden çok prosedür derleme zamanında üretilip çalışma zamanı değerine göre uygun prosedüre dinamik dispatch yapılabilir
  • Örnek kod, switch (runtime_val) içinde inline 0...100 aralığındaki değerleri staticFn(comptime_val)e gönderir, kalanları runtimeFn(runtime_val) ile işler
  • Binary boyutunun artmasını istemiyorsanız tamamen çalışma zamanı uygulamasına fallback yapılabilir

Sonuç

  • Zig’in comptimeı template, makro, generic ve elle kod üretiminin yerini alan bir rol üstlenir
  • Başka dillerle de benzer işler yapılabilir, ancak Zig’de comptime dile daha doğal biçimde entegre edilmiştir
  • Zig, gerçekten faydalı durumlarda performanslı kod yazmayı kolaylaştırır; her şeyin mümkün olduğu ama ilginç işlerin zor olduğu Turing tar-pit ile tezat oluşturur
  • Dil savaşları konusunda, Turing tamlığının tek başına yeterli olduğu yönündeki geniş bakış ile insanların aynı zamanda tercih ettikleri dillere sahip olabileceği görüşü birlikte kalır
  • “C, Python’dan hızlıdır” gibi dili kendi başına benchmark hedefi gören ifadeler yanıltıcı olabilir; gerçek benchmark hedefi dil değil, belirli kod ve uygulamadır

1 yorum

 
GN⁺ 2025-06-08
Hacker News yorumları
  • Zig’de beni en çok çeken yan, derleme sisteminin kolaylığı, çapraz derleme ve hızlı yineleme hızını hedeflemesi.
    Oyun geliştiricisiyim, bu yüzden performans gereksinimlerim var; ama çoğu dilin gereken performansı yeterince sağladığını düşündüğümden dil seçiminde birinci önceliğim bu değil.
    Her dille güçlü kod yazılabilir; asıl mesele, onlarca yıl modüler kodu sürdürebilecek geleceğe dayanıklı bir framework seçmek.
    C/C++ her yerde desteklendiği için varsayılan yanıttı; Zig’in de o seviyeye ulaşabileceği hissi var.

    • Zig’i seviyorum ama uzun vadeli bakım yapılabilirlik ve modülerliğin, aksine en zayıf yanlarından biri olduğunu düşünüyorum.
      Zig kapsüllemeye karşı ve struct üyelerini özel yapamıyorsunuz: https://github.com/ziglang/zig/issues/9909#issuecomment-9426...
      Temel alıntıdaki yaklaşım şu: “özel alanlar ve getter/setter’lar Java’nın popülerleştirdiği bir anti-pattern’dir; alanlar var olan veridir, bu yüzden dikkatle adlandırın ve genel API’nin parçası olarak belgeleyin.”
      İç temsili gizleyemiyorsanız, yazılım modülerliğinin temeli olan API sözleşmesini düzgün kurmak zorlaşır; kullanıcı kodunu bozmadan iç temsili değiştirebilmek gerekir.
      Zig’in tutumu, ayrı bir iç temsil diye bir şey olmaması; temsilin kendisinin açık, belgelenmiş ve garanti edilmiş olması gerektiği yönünde görünüyor. Umarım bir gün bu kararı geri alıp özel alanları destekler.
    • Eğlencesine eski bir Kindle cihazında, kırpılmış bir Linux 4.1.15 üzerinde Zig çalıştırmayı denedim; oldukça ilginç bir deneyimdi ve Zig’in olgunluğu beni hoş bir şekilde şaşırttı.
      Pek çok şey doğrudan çalıştı; eski bir GDB ile garip bir hatayı bile debug edebildim.
      Ben de Zig’e ikna oldum; bununla ilgili burada yazdım: https://news.ycombinator.com/item?id=44211041
    • Rust’ı biraz denedim ve beğendim; ama kötü bir itibarı olduğunu duyup bir süre ara verdim, sonra yeniden deneyince hâlâ hoşuma gitti.
      Neden bu kadar nefret edildiğini pek anlamıyorum.
      Göze çirkin gelen generic’ler C#’ta da TypeScript’te de var; borrow checker ise düşük seviye iş yaptıysanız anlaşılır bir kavram.
    • Zig daha basit bir Rust ve daha iyi bir Go gibi görünüyor.
      Zig üzerine yapılmış araçlar arasında gerçekten hayran kaldığım şey bun; bun’ı kullanmaya başladıktan sonra hayatım çok daha basitleşti.
      Rust ile yapılmış uv için de benzer şeyler söylenebilir.
    • Zig’in konsollarda nasıl çalışacağını merak ediyorum.
      Konsollar genelde C/C++ olmayan şeyleri sevmez; ama Zig C’ye dönüştürülebildiği için belki tamamen dışlanmayabilir.
  • “Modern derleyiciler bile dil spesifikasyonunu ihlal ediyor (Clang, yan etkisi olmayan tüm döngülerin sonlandığını varsayıyor)” iddiasına gelince; derleyicilerin zaman zaman spesifikasyonu ihlal ettiğinden şüphem yok, ama bu durumda Clang en azından C11 ve sonrası açısından haklı.
    C11’de, denetim ifadesi sabit ifade olmayan ve giriş/çıkış, volatile erişim, senkronizasyon ya da atomik işlem yapmayan döngüler için implementasyonun bunların sonlandığını varsayabileceği söyleniyor.

    • C++26 çıkana kadar C++ tüm döngüler için bunu söyleyecek; ama belirttiğin gibi C’nin kendisi böyle değil, yalnızca “denetim ifadesi sabit ifade olmayan” durumlar için geçerli.
      Bu yüzden C’deki basit sonsuz döngü for (;;); gerçekten sonsuz döngü olarak derlenmeli; Rust’ın daha az opak loop {} ifadesi de öyle olmalı.
      Ama LLVM, her zaman C++ derleyicisi yapmadıklarını unutan insanlar tarafından geliştirildiği için, Rust “bana sonsuz döngü ver” dediğinde LLVM’in “C++’a göre böyle bir şey yok, optimize edeceğim” dediği bir nokta vardı; bu da başka bir dile yanlış uygulanmış bir durumdu.
  • String karşılaştırmasını inline etmek ve açmak için illa comptime gerekmez.
    C’de de mümkün: https://godbolt.org/z/6edWbqnfT
    Yazım hatasını düzelttim.

  • JavaScript örneğiyle Zig/Rust örneklerini karşılaştırmanın iyi bir karşılaştırma olmadığını düşünüyorum
    Zig ve Rust derleyicilerine çok modern bir hedef CPU seçmeleri söylenmiş, ama V8 aynı koşullarda değil gibi görünüyor
    Optimizasyon yapan JIT de koşullar uygunsa vektörleştirme yapabilir
    Bu arada modern dillerin çoğu string’ler için aynı optimizasyonu uygular; C++ örneği burada: https://godbolt.org/z/TM5qdbTqh

    • Genel olarak elmalarla meyve salatasını karşılaştırmak gibi hissettiriyor, ama JS ile Zig’in kullanım alanı farkını göstermek için uygun
      Zig örneği sabit boyutlu, tipi bilinen bir dizi kullanıyor; JS kodu ise çalışma zamanında “generic”, yani x ve y herhangi bir nesne olabilir
      JS’de bu bedeli ödemek gerektiği doğru, ama ironik biçimde bu özel örnekte tip bilgisini JIT’e daha iyi aktarabilirsiniz
      Bu fonksiyonu her zaman aynı boyutta Float64Array ile çağıracak şekilde düzenlerseniz JIT bunu bilir ve daha hızlı bir döngü üretebilir. Vektörleştirme olmasa bile çok daha iyi olur
      Ancak typed array’lerin başlatma maliyeti yüksek olduğundan, büyük bir typed array’i bir kez ayırıp çok kez yeniden kullanmadıkça pratikte pek kullanılmaz
      Ayrıca yazıda JS bytecode’unun epey şiştiği söylenmiş; muhtemelen JIT’in 65536 değerinin iki dizinin uzunluğuna eşit olduğunu garanti edemeyip guard eklediği kısım bunun büyük nedeni
      Yine de gerçekte kimse for döngüsünü öyle yazmaz, i < x.length diye yazar; bu durumda JIT en azından dizi kontrollerinden birini kaldırır
    • Rust ve Zig’in godbolt örneklerinde target daha eski bir CPU’ya değiştirilebilir
      JS hedefinin sınırlamalarını düşünemediğim için üzgünüm
      Bağladığın C++ örneği Clang’in C++’ta neler yapabildiğini gösteren iyi bir örnek, ama Zig’in belirli bir CPU’yu hedefleyerek derlediği düşünülse bile üretilen assembly biraz hayal kırıklığı yaratıyor
      https://github.com/RetroDev256/comptime_suffix_automaton için bir C++ portu görmek çok ilginç olurdu
      Bu, C++ derleyicisinin temiz biçimde çıkarım yapamayacağı bir comptime kullanımı
  • “Yüksek seviyeli dillerde, düşük seviyeli dillerde bolca bulunan niyet eksik” cümlesinin gerçekten doğru olup olmadığından emin değilim
    Niyeti ifade etme yüksek seviye/düşük seviye spektrumunun bir unsuru değil gibi; aksine niyeti daha ayrıntılı ifade etmenin ne kadar çok yolu varsa, o kadar yüksek seviyeye yakın olmalı

    • Katılıyorum ve daha da ileri giderek, yüksek seviyeli dillerle düşük seviyeli diller arasındaki temel farkın, yüksek seviyeli dillerde niyetin ifade edilmesi; düşük seviyeli dillerde ise alttaki mekanizmayı ifade etmek zorunda kalınması olduğunu düşünüyorum
    • Burada kastedilen niyet “bu satın almanın vergi oranını hesapla” değil, daha çok “bu byte’ı sola doğru üç basamak kaydır” gibi görünüyor
      Neyin başarılmak istendiğinden ziyade makineye ne yaptırmak istediğiniz konusundaki niyet
      purchase.calculate_tax().await.map_err(|e| TaxCalculationError { source: e })?; gibi bir kod niyetle dolu, ama sonunda hangi makine kodunun çıkacağını bilemezsiniz
  • Şu for döngüsü sözdizimi korkunç
    Yan yana iki liste var ve bir listedeki öğenin konumu diğer listedeki öğenin konumuna karşılık geliyor, öyle mi?
    Sadece bakmak bile gözümü yoruyor
    Modern diller parser’a türlü “sihirler” eklemeye ve kodun her yanına küçük semboller serpiştirmeye başladığından beri yanlış yola girmiş gibi
    Saatlerce bakmak isteyeceğim bir biçim değil

    • Böyle diziler, dilden bağımsız olarak düşük seviyeli kodda çok yaygın bir kalıptır; paralel biçimde dolaşmak da öyle
      Bu yüzden Zig’in tam da bu işi kolaylaştıran, ama ne olduğunu da açıkça gösteren bir sözdizimi sunması doğal
      Kişisel olarak oldukça iyi yapıldığını düşünüyorum; neden gözünü yorduğunu merak ediyorum
  • Zig’in allocator modeli gerçekten hoşuma gidiyor
    Go’da da garbage collection yerine istek başına allocator gibi bir şey kullanabilsek iyi olurdu

    • Go’da da custom allocator’lar ve arena’lar mümkün ve gerçekten var, ama kullanışlılıkları çok kötü ve doğru kullanmaları zor
      Dilin kendisinde sahiplik kurallarını ifade edip zorlayacak bir yol olmadığından, sonunda yalnızca sözdizimi biraz farklı olan C yazıp işlerin yolunda gitmesini ummak gibi oluyor
      Garbage collection olmadığı durumda C++ bile Go’dan çok daha güvenli
  • Rust’ın bellek modeli, derleyicinin fonksiyon argümanlarının asla alias referansı olmadığını her zaman varsayabilmesini sağlar; Zig’de ise bunu elle belirtmek gerekir
    Bu tür alias belirtimlerinden kaçınılıyor
    Çünkü anlayan kişi az ve yanlış kullanılırsa kodda anlaşılması zor bug’lar oluşabilir

  • Zig’in comptime esnekliğinin başka dillere iyi iyileştirmeler getirdiği iddiasına gelirsek, derleme zamanında fonksiyon çalıştırma ve sabit argüman alan fonksiyonları D 2007’de tanıttı ve birçok dilin benzer şeyler benimsemesine yol açtı
    https://dlang.org/spec/function.html#interpretation

  • Zig’i seviyorum ama “Zig’in laf kalabalığını seviyorum” demek kulağa garip geliyor
    C’nin birçok köşede fazla gevşek olduğu doğru, ancak Zig mevcut hâliyle biraz fazla ters yöne savrulmuş durumda ve bazen yorum gibi duran tip belirtimi gürültüsü çok artabiliyor
    Özellikle ifadelerde açık tamsayı cast’leri böyle; bununla ilgili burada biraz yazmıştım: https://floooh.github.io/2024/08/24/zig-and-emulators.html
    Performans açısından Zig kodu benzer C kodundan daha hızlıysa, bu genellikle Zig’in daha agresif LLVM optimizasyon ayarlarından kaynaklanır
    Örneğin Zig varsayılan olarak -march=native ve tüm program optimizasyonu kullanır, ayrıca projedeki tüm Zig kodunu tek bir derleme birimi olarak derler
    unreachable’ı optimizasyon ipucu olarak kullanmak gibi neredeyse tüm “numaralar” C’de de mümkündür; yalnızca zaman zaman standart dışı dil uzantıları gerekir
    C derleyicileri, özellikle Clang, sabit katlamayı da çok agresif yapar ve derin çağrı yığınları olsa bile sabite katlanabilen büyük kod bölgelerini küçültebilir
    Bu yüzden yalnızca kod üretimine bakıldığında çoğu zaman Zig’in comptime’ı ile büyük bir fark yoktur
    comptime’ın avantajı, sessizce çalışma zamanı koduna geri düşmemesidir; comptime olmayan kod da C’deki gibi aynı sabit katlama optimizasyonlarına tabi olur
    Örneğin “saf” bir non-comptime fonksiyon sabit argümanlarla çağrılırsa derleyici yine de fonksiyon çağrısını sonucuyla değiştirir
    Özetle C kodu Zig kodundan yavaşsa C derleyici ayarlarını kontrol etmek gerekir. Sonuçta optimizasyonun ağır işi LLVM’in altında gerçekleşir

    • Cast örneğiyle ilgili olarak cast’i bir fonksiyonla sarmalayabilirsiniz
      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); }
      Aynı assembly’ye derlenir, yeniden kullanılabilir ve niyeti de daha net hâle getirir
    • Zig’de ilginç fikirler var ve yazının düşük seviyeli optimizasyona daha fazla odaklanacağını sanmıştım, ama sonunda “comptime ve tüm program derlemesi harika” içeriğine dönüştü
      Buna katılıyorum
      Virgil 2006’dan beri tüm dili derleme zamanında kullanabiliyor ve tüm program derlemesini de destekliyordu
      Ancak Virgil LLVM’i hedeflemediği için hız karşılaştırması eninde sonunda iki derleyici arka ucunun karşılaştırmasına dönüşüyor
      Virgil, bu derleme modelinin mümkün kıldığı erişilebilirlik analizi ve özelleştirme optimizasyonlarına büyük ölçüde yaslanıyor
      Örneğin metot çağrılarını agresif biçimde devirtualize eder, erişilemeyen alanları ve nesneleri kaldırır, alanlar ve heap nesneleri üzerinden sabit yükseltmesi yapar ve polimorfik kodu tamamen monomorfik hâle getirir
    • Yeni x86 arka ucu çıktığında, C ile Zig arasındaki performans farkının Zig projesinin kendisine atfedilebilecek kısmını görebiliriz
    • Açık tamsayı cast’leriyle ilgili olarak yakında bir düzenleme gelecek gibi görünüyor: https://ziggit.dev/t/short-math-notation-casting-clarity-of-...
    • Önümüzdeki birkaç yılda dil tasarımlarındaki genel eğilimin daha lafı uzatan ve daha açık diller yönüne oldukça güçlü biçimde kayacağını düşünüyorum
      Bunun tek nedeni, yapay zekanın bunları daha kolay işlemesi
      Kodlamada yapay zeka kullanmanın iyi bir fikir olup olmadığı ya da ne ölçüde iyi olduğu ayrı bir mesele; ancak birçok geliştirici buna inanıyor ve diller de onları kucaklamaya çalışacak