- Yazar, Zig dilini öğrenirken AcoustID indeksini yeniden yazma projesi üzerinde çalıştı ve ağ programlamasındaki sınırlamalar nedeniyle yeni bir yaklaşım denedi
- Daha önce C++ ve Go'da kullandığı asenkron I/O ve eşzamanlılık modelini Zig'de de uygulamak için kendi kütüphanesini geliştirmeye karar verdi
- Bunun sonucunda, Go tarzı eşzamanlılık modelini Zig'e uygun şekilde uygulayan Zio kütüphanesini geliştirerek callback olmadan senkronmuş gibi görünen asenkron kod yazmayı mümkün kıldı
- Zio; asenkron ağ ve dosya I/O'su, kanallar, senkronizasyon primitifleri, sinyal izleme gibi özellikleri destekliyor ve tek iş parçacıklı modda Go veya Rust'ın Tokio'sundan daha hızlı performans gösteriyor
- Bu proje, Zig'in sistem düzeyi performans ile modern eşzamanlılık modelini birleştirme potansiyelini gösterirken, Zig ekosisteminin genişlemesi açısından önemli bir dönüm noktası olarak değerlendiriliyor
Zig dili ve ilk motivasyon
- Yazar, başlangıçta ses yazılımları için düşük seviyeli bir dil olarak tasarlanan Zig'i izliyordu, ancak somut bir ihtiyaç hissetmiyordu
- Zig'in yaratıcısı Andrew Kelley'nin yazarın Chromaprint algoritmasını Zig ile yeniden uyguladığını görünce ilgisi arttı
- AcoustID'nin ters indeks yeniden yazım projesini Zig öğrenmek için bir fırsat olarak ele aldı ve sonuçta C++ sürümünden daha hızlı ve daha ölçeklenebilir bir uygulama elde etti
- Ancak sunucu arayüzü ekleme aşamasında asenkron ağ desteğinin yetersizliği sorunuyla karşılaştı
Mevcut yaklaşımlar ve sınırlamalar
- Önceki C++ sürümünde asenkron I/O için Qt framework'ü kullanıyordu; callback tabanlı olsa da sunduğu zengin destek sayesinde kullanılabiliyordu
- Daha sonraki prototiplerde Go dilinin ağ ve eşzamanlılık kolaylığından yararlandı, ancak Zig'de benzer düzeyde soyutlama bulunmuyordu
- Zig ile TCP sunucusu ve küme katmanı uygulamak, çok sayıda iş parçacığı oluşturmayı gerektiren verimsiz bir yapıya yol açıyordu
- Bunu çözmek için NATS mesajlaşma sistemi için Zig istemcisini (
nats.zig) doğrudan yazarak Zig'in ağ özelliklerini derinlemesine inceledi
Zio kütüphanesinin ortaya çıkışı
- Bu deneyimlerin ardından Zio: Zig için asenkron I/O ve eşzamanlılık kütüphanesini duyurdu
- Zio, callback'siz asenkron kod yazmayı hedefliyor; içeride asenkron I/O çalışırken dışarıdan senkronmuş gibi görünen bir yapı sunuyor
- Go tarzı eşzamanlılık modelini Zig'e uygun biçimde sınırlı olarak uyguluyor
- Zio'daki task'ler, sabit boyutlu stack'e sahip stackful coroutine'ler biçiminde çalışıyor
stream.read() çağrıldığında I/O işi arka planda yürütülüyor, tamamlanınca task yeniden başlatılıyor ve sonuç döndürülüyor
- Bu yaklaşım aynı anda hem durum yönetimini basitleştiriyor hem de kod okunabilirliğini artırıyor
Özellik seti ve runtime yapısı
- Zio; tam asenkron ağ ve dosya I/O'su, senkronizasyon primitifleri (mutex, condition variable vb.), Go tarzı kanallar, OS sinyal izleme gibi özellikleri destekliyor
- Task'ler tek iş parçacıklı veya çok iş parçacıklı modda çalıştırılabiliyor
- Çok iş parçacıklı modda task'ler iş parçacıkları arasında taşınabiliyor; bu da gecikmeyi azaltma ve yük dengelemesini iyileştirme etkisi sağlıyor
- Standart Reader/Writer arayüzlerini uygulayarak dış kütüphanelerle uyumluluk sağlıyor
Performans ve karşılaştırma
- Yazar henüz resmi benchmark yayımlamış değil, ancak tek iş parçacıklı modda Go ve Rust'ın Tokio'sundan daha hızlı performans gördüğünü belirtiyor
- Context switch maliyeti fonksiyon çağrısı seviyesine kadar düşük; geçiş hızı neredeyse ücretsiz denecek kadar yüksek
- Çok iş parçacıklı mod henüz Go/Tokio kadar sağlam değil, ancak benzer veya biraz daha iyi performans gösteriyor
- İleride fairness özelliği eklendiğinde performansın bir miktar düşme ihtimali var
Örnek kod ve kullanım
- Belgede Zio tabanlı bir HTTP sunucusu örnek kodu yer alıyor
zio.net.Stream ile bağlantılar kabul ediliyor ve her bağlantı ayrı bir task tarafından işleniyor
zio.Runtime, task yürütme ile I/O zamanlamasını yönetiyor
- Bu yapı, asenkron I/O'nun senkron kod gibi yazılmasını sağlarken akış kontrolünü ve kaynak serbest bırakmayı daha net biçimde yönetmeye olanak tanıyor
Gelecek planları ve önemi
- Yazar, Zio sayesinde Zig'in yalnızca yüksek performanslı sistem kodu için bir dil olmanın ötesine geçip, tam teşekküllü bir ağ uygulaması geliştirme dili haline gelebileceğini gördüğünü söylüyor
- Bir sonraki adım olarak NATS istemcisini Zio tabanlı yeniden yazmayı ve Zio tabanlı HTTP istemci/sunucu kütüphaneleri geliştirmeyi planlıyor
- Bu proje, Zig ekosisteminin ağ ve eşzamanlılık altyapısının genişlemesine öncülük ederken, Go ve Rust ile kıyaslanabilecek modern bir runtime modeli kurma girişimi olarak değerlendiriliyor
1 yorum
Hacker News görüşleri
Zig'in async tasarımının donanım düzeyindeki call/return çiftlerini mi kullandığı, yoksa dolaylı atlama temelli mi derlendiği net değil
Kusursuz bir benchmark için, iki görev arasında sürekli geçiş olan bir programın toplam çalışma süresiyle tamamen senkron bir programınkini karşılaştırmak gerekir. Bu da epey zor bir iştir
Derleyiciyi kontrol edebiliyorsanız, I/O kodundaki call/ret'i açık sıçramalara dönüştürmek de mümkündür
Uzun vadede CPU'nun stackful coroutine'leri daha iyi tahmin etmesi için bir meta-predictor eklenmesini umuyorum
İlgili yazı: Zig new async I/O
Ben Zig'i embedded (ARM Cortex-M4, 256KB RAM) ortamında kullanıyorum ve C ile birlikte çalışmada bellek güvenliğini sağlamak için tercih ediyorum
Rust'taki gibi renkli async'i daha çok seviyorum. Senkron kod gibi görünen o sihirli his güzel, ama büyük kod tabanlarında hangi işlevin blocking olduğunu ayırt etmek zorlaşıyor
CPU I/O yüzünden gerçekten bloklanmaz; OS thread'leri de aslında işletim sisteminin uyguladığı stackful coroutine'lerdir
Dil düzeyinde bu illüzyonu daha verimli uygulayabilmemiz dışında öz değişmiyor
Bir işlevin renkli olup olmadığı, I/O yapıp yapmamasına göre belirlenecek; çağrı anında da async olup olmadığı açıkça belirtilecek
Zig ayrıca işlev çağrısı sırasında gereken yığın boyutunu hesaplamayı da hedefliyor; bu sayede stackful coroutine'lerin RAM israfı sorununu azaltabileceği umuluyor
Yine de proje hâlâ çok aktif ve hızlı sürüm çıkarmaktan çok doğru tasarımı öncelemesini olumlu buluyorum
Şimdilik Go ya da C kullanıp 1.0'ı bekliyorum
Ben de I/O ağırlıklı işler için 0.16'yı bekleyeceğim
Mevcut kod çalışmaya devam ediyor ve yeni API daha ergonomik ve performanslı
Ben de mevcut projeyi yeni Reader/Writer API'sine taşırken kodun çok daha temiz hâle geldiğini gördüm
libtask benzeri yaklaşım çok daha temiz görünüyor
Rust da callback tabanlı async'i benimsedi ama nedenini pek anlayamıyorum
Referans: libtask
Ama yığını doğrudan ele aldığınızda exception handling, GC, debugger gibi şeylerle çakışabilir
Ayrıca LLVM düzeyinde bu tür değişiklikleri birleştirmek de zor olduğundan, dil tasarımcıları açısından pratik kısıtlar fazladır
Çok küçük olursa taşma, çok büyük olursa bellek israfı olur
Gerekli yığın boyutu platformdan platforma değiştiği için taşınabilirlik sorunu da doğar
Zig sorunu #157 çözülürse, bu yaklaşım daha iyi hâle gelebilir
Yani async'i uygulamanın üç yolu vardır
Rust statik bir durum makinesine dönüştürülür ve runtime bunu poll eder
Stackful yaklaşımda bellek israfı büyüktür ve yığın boyutunu yönetmek zordur
Rust bunu önlemek için stackless bir yapı seçti; Zig ise iki yaklaşım arasında seçim yapılmasına izin vermeyi planlıyor
Referans: zio coroutine kodu
setsockoptile okuma/yazma timeout'u ayarlayabilirsinizZig bir POSIX API katmanı sağlıyor
Referans: setsockopt belgesi
Python'daki
asyncio.timeoutgibi çalışan bir yapı tasarlanıyorÖrnek kod:
Oysa en zor kısım aslında tam da bu
Referans: zio.dev
Ama Zig, düşük seviyeli bir dil olmasına rağmen yüksek seviyeli API'leri temiz şekilde ifade edebilmesiyle beni etkilemişti
Hem Zig hem de Go için yeni Qt binding'leri çıktı
Ben Rust için binding istiyorum. Yalnızca cxx-qt aktif olarak sürdürülüyor ama QML ya da CMake kullanmak istemiyorum. Qt'yi yalnızca Rust + Cargo ile kullanmak istiyorum