- WebAssembly, Photoshop gibi büyük C++ programlarını web'e taşımada başarı sağladı, ancak DOM merkezli uygulamalarda JavaScript'ten farklı programlama modeli nedeniyle yaygınlaşması sınırlı kaldı
- Tarayıcıların Wasm GC ve referans tipleri desteği, Python ve Scheme gibi yönetilen bellek kullanan dillere fırsat açıyor; ancak web'de aktarım boyutu başlı başına benimseme engeli oluyor
- Go'nun basit bir Wasm programı 2MB, import eklendiğinde 10MB'ın üzerine çıkabiliyor ve Pyodide REPL yaklaşık 20MB indirerek sıradan web uygulamaları için ağır kalıyor
- Hoot Scheme derleyicisi, GC destekli Wasm'ı hedefliyor ve en küçük
mainderleme birimini yaklaşık 70KB'a kadar indirdi; yardımcı derleme birimleri ise 1KB'ın altına inebiliyor - Etkili tree shaking, yalnızca referans verilmeyen fonksiyonları silmenin ötesinde, akış analizi ile standart kütüphane tasarımının birleştiği bir derleyici problemi
WebAssembly'nin web'de başarı sağladığı alanlar
- WebAssembly, web'de ilk beklentiler kadar geniş yayılmadı, ancak bazı alanlarda sınırlı başarı elde etti
- Temel örnek, Photoshop gibi büyük C++ programlarının web'e taşınması
- Figma da 5 yıl önceki bir Wasm örneği olarak anılıyor, ancak bugün Wasm'ı çok öne çıkarmıyor
- C++ veya Rust'tan derlenen küçük NPM kütüphanelerinin birçoğu içeride Wasm kullanıyor
- Blazor, bazı kurum içi kurumsal uygulamalarda kullanılıyor olabilir, ancak pazarlama tarafında abartılmış olma ihtimali de var
- Unreal Engine'in 3D FPS demosu, 5 yıl önceki önceki ana sürüme dayanan bir deneydi ve bugün Unreal 5 WebAssembly hedefini desteklemiyor
DOM merkezli uygulamalarda Wasm neden tıkandı
- WebAssembly, web dışındaki ortamlarda başarı sağlıyor ve web platformunda da önem kazanabilir, ancak web'de hâlâ hayal kırıklığı çukurundan yeni çıkıyor denebilir
- Wasm, JavaScript'in iyi yapamadığı işlerde veya istemci ile sunucu arasında ortak uygulama gereken durumlarda güçlü
- DOM merkezli uygulamalarda ise Wasm başarılı olamadı
- wordpress.com frontend'ini Wasm ile baştan yazalım gibi bir öneri gündeme gelmiyor
- Web'in ana programlama modeli, dinamik tipli ve yönetilen bellekli JavaScript
- WebAssembly 1.0 ise statik tipler ve lineer bellek etrafında tasarlandı
- Wasm içinden DOM'a erişmek, ancak en hevesli Wasm savunucularının katlanacağı kadar zahmetliydi
- C# gibi diller, çöp toplayıcıyı da beraber dağıtmak zorunda; bu da C/Rust dışı dillerin Wasm benimsemesinde engel oldu
Wasm GC sonrasında da süren aktarım boyutu sorunu
- Tarayıcıların önümüzdeki birkaç ay içinde referans tipleri ve çöp toplama desteği sunması bekleniyor
- Chrome ve Firefox zaten Wasm GC sunuyor
- Safari'nin de Asumu Takikawa'nın çalışmaları sayesinde çok uzak olmadığı düşünülüyor
- Wasm GC, daha fazla dilin WebAssembly desteği için araç zincirlerini güncellemesini sağlayacak bir değişim
- Web'de Wasm'ın başarılı olması için derleyicilerin küçük kod üretmesi gerekiyor
- Dil araç zincirleri, aktarım açısından birkaç KB seviyesinde işe yarar Wasm dosyaları üretebilirse avantajlı olur
- Aksi halde aşırı beklentilere veya esir kullanıcı kitlesine yaslanmak gerekir ve bir sonraki çözüm bulunana kadar kırılgan bir dengede kalınır
- JavaScript ekosisteminde aktarım boyutu ve şişmeyi azaltmaya yönelik araç endüstrisi zaten büyük
- esbuild gibi bundler'lar birçok JS modülünü tek dosyada birleştiriyor
- Yalnızca kullanılan fonksiyonları ve veri tiplerini dahil etmeye çalışıyor
- Minification gibi isimleri kısaltan boyut küçültme stratejileri de uygulanıyor
Tree shaking adının kurduğu tuzak
- Tree shaking, bir sayfada gerekli olan kodun kalıp geri kalanının dökülmesi şeklinde görsel bir metafor sunuyor
- Bu metaforda modüller dallar, tanımlar yapraklar gibi düşünülse de gerçek bir ağaç gövdesinden tutulup sallandığında hangi dalların gerekli, hangilerinin gereksiz olduğunu söylemez
- Adın kendisi, gereksiz kodu kaldırma yönünde düşünmeye itiyor; oysa algoritmik açıdan gerekli kodu koruyan bir sabit noktayı bulmak daha uygun bir yaklaşım
- Buna rağmen tree shaking çarpıcı bir ad olduğu için, bahçecilik ve algoritma açısından isabetsiz olsa da kullanılmayı sürdürüyor
Kalın runtime'ın yarattığı boyut engeli
- Runtime'ı ağır olan dillerde, mümkün olan en güçlü tree shaking büyük bir öncelik değildi
- Go'nun WebAssembly desteğinde en basit program bile golang wiki'ye göre 2MB
- import eklendiğinde bu 10MB'ın üstüne çıkabiliyor
- Python'un WebAssembly portu olan Pyodide'ın REPL örneği yaklaşık 20MB veri indiriyor
- Bu boyutlar teknik demolar veya çok zengin uygulamalar için kabul edilebilir, ancak genel amaçlı bir web geliştirme seçeneği olmaları zor
Alternatif araç zincirleri ve platforma uyarlanmış uygulamalar
- Go'nun yerleşik Wasm desteği ile Pyodide, upstream araç zincirlerinden türetildi; sunucu tarafında ikili boyutu çok önemli olmayabilir
- Küçük cihazları hedeflerken ayrı uygulamalar ortaya çıkıyor
- TinyGo'nun Wasm backend'i 1KB'ın altına kadar inebiliyor gibi görünüyor
- Bu tür alternatif araç zincirleri çoğu zaman kısıtlar veya tuhaflıklar getiriyor
- DOM ortamında çalışan Wasm hedefli Python programları, “yerel” Python programlarıyla aynı olamaz
- Araç zinciri yazarları aynı dili sunmaya çalışsa da standart kütüphane uygulamaları farklı olabilir
- ClojureScript geliştiricileri de mümkün olsa Clojure'dan farklar belgelerini kaldırmak isterdi; Wasm, ClojureScript için pratik bir hedef olursa bu mümkün olabilir
Hoot Scheme'in tree shaking yaklaşımı
- GC desteğinden sonra Wasm, Python benzeri dillerle DOM programlamayı düşünmeyi mümkün kılıyor; ancak yaygın kullanım için küçük modüller gerekli
- Hoot Scheme compiler GC'li Wasm'ı hedefliyor
- Şu anda en küçük
mainderleme birimi yaklaşık 70KB - Daha da küçültmek hedefleniyor
- İstisna işleyicileri gibi runtime özelliklerini main modülünden import eden yardımcı derleme birimleri 1KB'ın altına düşebiliyor
- Şu anda en küçük
- Hoot derleyicisi, kullanıcı kodunun önüne bir prelude ekliyor
- Tree shaking birden fazla aşamada gerçekleşiyor
- Kısmi değerlendirme, kullanılmayan binding'leri etkilerini değerlendirdikten sonra kaldırabiliyor
- fixing letrec de benzer bir iş yapıyor
- CPS, programı sık sık dolaşarak yalnızca referans verilen fonksiyonları, değerleri ve kontrol akışı kenarlarını izliyor
- Açık bir dead-code elimination geçişi, diğer optimizasyonlardan sonra oluşabilecek kullanılmayan ve etkisiz atamaları kaldırıyor
- Neredeyse ham WebAssembly ile yazılmış standart kütüphane tanımları yalnızca gerektiğinde sonuç ikili dosyasına dahil ediliyor
Kolay kaldırılanlar ve zor kaldırılanlar
- Fonksiyonlar ve closure'lar gibi prosedür tanımları nispeten daha kolay ele alınıyor
- Kodun referans verdiği fonksiyonları dahil etmek yeterli
- Scheme gibi dillerde bu tek başına bile önemli etki yaratıyor
- Hemen görülen üç zorluk var
-
letrec* değerlendirme modeli
- Prelude tanımlarının scope'u özyinelemeli ama sıralı
- Binding değerleri, daha önce tanımlanan değerleri çağırabilir veya referans verebilir ve daha sonra tanımlanacak değerleri de capture edebilir
- Bir binding değerini değerlendirmek için ancak daha sonra tanımlanan bir değere referans vermek gerekiyorsa bu hata olur
- Prosedürlerde bu genelde sorun olmaz, ancak prosedür olmayan tanımlarda derleyici “yalnızca önceki binding'lere referans verir” özelliğini kanıtlayamayabilir
- Bu durumda fixing letrec reloaded algoritması
set!yapılmış binding'leri bırakabilir; bunları temizlemek için ince ayarlı bir DCE geçişi gerekir
-
Kayıt tiplerinin vtable'ı
- Bazı prosedür olmayan tanımlar kayıt tipleridir
- Kayıt tipleri, kayıtların nasıl yazdırılacağını veya örnek denetiminin nasıl yapılacağını taşıyan bir vtable içerir
- vtable callback'leri gerçekte kullanılmasa bile çok sayıda kod parçasını canlı tutabilir
-
Polimorfik çıktı fonksiyonları
displaygibi polimorfik fonksiyonlar, gereken kod kapsamını ciddi biçimde genişletir- Bir string yazdırmak için
displayçağrıldığında tüm buffered I/O altyapısı da gelir displayher şeyi yazdırabildiği için bitvector, pair ve başka pek çok durum için kod da beraber çekilir- Yalnızca string kullanan
write-stringçağrılırsa genel veri yazdırma kodundan kaçınılabilir, ancak port gibi genel buffered I/O altyapısı yine dahil olur
En iyi tree shaking bir akış analizi problemidir
- En iyi tree shaking sonuçta bir akış analizi problemidir
- Programda hiç bitvector yoksa,
displayiçindeki bitvector işleme kodu ölü kod olabilir - Bunu bilmek için
display'in hangi tür argümanlarla çağrıldığını bilmek gerekir; bunun için de yüksek seviyeli akış analizi gerekir - Python'da sorun daha da zorlaşıyor
- Nesne yönelimli dispatch, yüksek dereceli programlama olduğundan
foo.barifadesinin ne anlama geldiğifoo'nun ne olduğuna bağlı - Python'daki lookup, Scheme'e göre daha dinamiktir ve
__getattr__gibi metotlar her yerde kullanılabilir - Pratikte akış analizinin bu dinamik lookup'ları dışlayıp dışlayamayacağı ayrı bir soru
- Python'da tree shaking hedefi, leksik binding'leri olan büyük birimlerden çok modüllerin karmaşık bir kümesidir
- Bu JavaScript'e benziyor, ancak Python için yerleşik bir tree shaking bundler ekosistemi yok
- Nesne yönelimli dispatch, yüksek dereceli programlama olduğundan
Web Wasm dil araç zincirlerinin gereklilikleri
- Wasm GC, JavaScript dışındaki dillerle DOM programlamayı mümkün kılabilir
- Bunun yaygın kullanıma dönüşmesi için ortaya çıkan Wasm modüllerinin küçük olması gerekir
- Her dil araç zinciri önemli yatırım ister
- Bu yatırımlar, çoğu zaman deneysel tree shaking algoritmaları içeren alternatif araç zincirleri olarak ortaya çıkar
- Alternatif standart kütüphanelerin, tree shaker'ın daha iyi çalışacağı şekilde tasarlanması gerekir
1 yorum
Hacker News yorumları
openEtG’nin Wasm blob’unu (kart oyunu motoru) 400 KB’ın altında tutarken, kart metni üretimi gibi pek çok mantığı Wasm’a taşıdım ve Rust ile yazdım
Boyutu küçültmek için kayan nokta yerine sabit nokta aritmetiği kullanmak, hash map’ler yerine vektörlere geçmek, string’lerden kaçınmak,
talcgibi küçük bir allocator kullanmak ve bağımlılıkları azaltmak gibi bir yönetim gerekiyorduYalnızca
randvefxhashkullanıyorum;randde muhtemelen kaldırılabilir,fxhashise yalnızca oyun durumu hash’iyle senkron dışına çıkılıp çıkılmadığını kontrol etmek için kullanılıyorGeneric instance türlerini de azalttım; zaten
VecvarkenBox<[i16]>gibi tipleri ayrıca içeri çekmemeye çalıştım, kayan nokta ve hash map’i kaldırmak da tip çeşitliliğini azaltmaya yardımcı olduAlgoritmaları da boyutu dikkate alarak tasarladım; örneğin saldırı gücü düşük yaratıkların daha çok saldırmasını sağlayan adrenaline mekanizmasını bit paketlemeli lookup table ile kodladım
Sıkıştırılmamış değerleri saklamanın maliyetini decode mantığının maliyetiyle karşılaştırdım; yapay zeka değerlendirmesinde WebAssembly’de 64, 128’e göre daha verimli kodlandığı için 6 bit sabit hassasiyet kullanıyorum
Targeting mekanizması da eskiden her predicate’i enum olarak tutup AND/OR’u expression slice’larıyla ifade eden AST biçimindeydi; artık ifadeyi Polonya notasyonuyla 32 bitlik tamsayıya kodluyorum, AND/OR 2 bit, predicate ise 6 bit olarak tutuluyor
Burada ters Polonya notasyonuna kıyasla Polonya notasyonu daha iyiydi; çünkü AND/OR için kısa devre değerlendirme mümkün oluyordu
İş yerinde azami çözünürlük gereksinimlerini bildiğimiz problemlerde, örneğin milimetrenin altındaki konum hassasiyetine ihtiyaç olmayan durumlarda sabit noktanın yardımcı olup olmayacağını düşünüyorum; bu konudaki ilgili sorunları daha fazla duymak isterim
wasm-optaracını kullanmakWasm boyutunu istikrarlı biçimde yaklaşık %20–30 azaltıyor gibi görünüyor: https://github.com/WebAssembly/binaryen
Tarayıcıya Wasm bundle’ı sunarken Brotli sıkıştırması kullanmak ve web sunucusunu Brotli sıkıştırılmış dosyaları kullanacak şekilde ayarlamak da iyi olur
nginx kullanıyorsanız tek satırlık bir değişiklikle mümkün; Brotli, Wasm bundle boyutunu yaklaşık 3 kat küçültüyor ve gzip’ten çok daha iyi
Bu boyuta derin öğrenme ileri/geri yayılımı, pekiştirmeli öğrenme algoritmaları ve dinamik simülasyonu dahil
Şimdilik rahatsız edecek kadar büyük değil ama ne kadar daha küçültülebileceğini merak ettiğim için yakında boyutu azaltmayı denemek istiyorum
Kayan noktadan kaçınıp sabit nokta aritmetiğiyle alan tasarrufu sağlamak nasıl mümkün oluyor, anlamak istiyorum
Özellikle 6. maddede olduğu gibi
Vec’iBoxgibi kullanma yönteminin büyük bir tasarrufa yol açacağını sanmıyorumTree shaking adının oldukça hatalı bir adlandırma gibi göründüğü düşünülüyor
Virgil derleyicisi buna “erişilebilirlik analizi” diyor ve bunu derleme modelinin içine yerleştirmiş durumda
Derleyici programı ve kütüphane kodunu ayrıştırıp tip denetiminden geçiriyor ve başlatma kodunu çalıştırıyor; ancak sonrasında ana giriş noktasından başlayarak yalnızca erişilebilir kodu analiz edip nihai ikili dosyaya koyuyor
Çalışma zamanı sistemi olmadan, tek bir
mainişlevi olan programları da gayet iyi üretiyor; çalışma zamanı sistemi yalnızca yığın izleme ve çöp toplama için gerektiğinden istenirse atlanabiliyorBulabildiğim ilk örnek, UNIX için ticari bir Common Lisp uygulaması olan Lucid Common Lisp 4.1’in 1992 tarihli Treeshaker aracıydı
Lucid CL’de, çalışan Lisp heap’inin kaydedilmiş bellek dökümü olan “image” kavramı vardı ve uygulamalar bir image ile çalışma zamanından oluşuyordu
Image genellikle bellekteki kod ve verilerin neredeyse tamamını içerdiğinden, dağıtım için daha küçük image’lar oluşturma isteği doğuyordu; Treeshaker da image kaydedilmeden önce “kullanılmıyor” olduğuna karar verilen kod ve verileri kaldırıyordu
Erişilebilir Lisp verileri ve kodlarından oluşan grafikte bağlantıları budayıp, GC’nin ya da özel kodun çöpleri toplayarak belleği küçültmesini sağladıktan sonra daha küçük bir image olarak dökme biçimindeydi
Dolayısıyla Treeshaker bir derleyici aracı değil, Lisp heap’inden kullanılmayan kod ve verileri kaldıran bir araçtı
Varsayılan Lisp image’ı derleyiciyi, yorumlayıcıyı ve REPL uygulamasını bile içerdiğinden, çalışan programı kesip REPL’e girildiğinde image’dan geri yüklenen heap içindeki tüm kod hâlâ kullanılabiliyordu
Bu yüzden derleyiciyi veya REPL’i bile kaldırmak anlamlıydı
Erişilebilirlik analizi hangi kodun kaldırılabileceğine karar vermek için sıkça kullanılır; ancak analizin kendisi kodu kaldırmaz, kaldırma işlemi sonraki aşamadır
“Tree shaking” genellikle işlev düzeyinde kaldırmayı ima ederken, ölü kod kaldırma koşullu ifadelerde dalların kaldırılması gibi çok daha ince düzeylerde de yapılabilir ve çeşitli statik analizlere dayanabilir
İlk gördüğümde ek araştırma yapmadan anlamını hemen kavramıştım
Bir ağacı sallayıp gevşek bağlı olan şeylerin düşmesini sağlamak demek; burada da kullanılmayan paketlerin tree’den “sallanıp düşmesi” anlamı açık
Kaynak kod diyagramını fiziksel bir nesne olarak hayal ederseniz, sallandığında kökten erişilemeyen şeyler kopup düşer
Erişilebilirlik analizinden çok da farklı değil; yalnızca ifadelerden biri uzamsal akıl yürütmeyi daha çok çağrıştırıyor
Kodun bir kısmı giriş noktası olan gövdeye bağlı değil
Tree shaking bağlı olmayan kısımları, yani gevşek yaprakları ve ölü dalları kaldırır
Bir dilin derleyici araç zinciri, ağ üzerinden aktarım ölçütüyle birkaç KB’yi geçmeyen kullanışlı Wasm üretebiliyorsa yeni olasılıklar açılır
Çok küçük ikili dosyalar Wasm için yeni kullanım örnekleri açacak ve WasmGC kesinlikle yardımcı oluyor
Java ve Kotlin de bugün 2–3 KB civarında oldukça iyi sonuç verebiliyor: https://developer.chrome.com/blog/wasmgc, https://twitter.com/bashorov/status/1661377260274720770
Ancak hangi API’nin kullanıldığına bağlı olarak büyük miktarda kodun beraberinde gelebileceğine dikkat etmek gerekiyor
Yine de bu diller, WasmGC sayesinde bellek yönetimi kodunun birkaç KB’sini pakete koymak zorunda kalmadığından, kod boyutu açısından C++ ve Rust’tan şimdiden çok daha iyi durumda
JavaScript nesneleriyle çalışırken yardımcı olabilir ve daha az verimli bir alternatif bellek ayırıcı olarak da kullanılabilir gibi görünüyor
Yine de Wasm’da varsayılan Rust ayırıcı çoğu durumda muhtemelen yeterince iyidir
Boyut optimizasyonuna başlayıp
wasm-optkullanır ve Brotli ile sıkıştırırsanız, indirme boyutu 100 KB’nin altında kalacak şekilde muazzam miktarda kod sığdırabilirsiniz100 KB Wasm maliyetini 100 KB paket JavaScript ile doğrudan karşılaştırmak hatadır; çünkü JavaScript’in ayrıştırılması ve başlatılması birkaç kat daha yavaştır
İndirme süresi gerçek bir maliyettir, ancak ilk ekranın gösterilme süresi açısından 100 KB Wasm, 100 KB JavaScript’ten çok daha iyidir
Yine de ne kadar küçük olursa o kadar iyi; Java, Kotlin, C#, Python, Go gibi dillerin web uygulamalarında pratik diller hâline gelmesi heyecan verici
Gerçek uygulama boyutlarının nasıl olacağını da merak ediyorum
En büyük fark muhtemelen framework tasarımından gelecek; sanal DOM karşılaştırması, Svelte, SolidJS ve Rust’ın Leptos’u gibi reaktif bileşen kütüphanelerinden her zaman daha karmaşık ve daha yavaş olmak zorunda
WasmGC her yerde desteklendiğinde performans üzerinde dilden çok web framework seçiminin çok daha büyük etkisi olacak gibi görünüyor
“Wasm, DOM programlamayı JavaScript dışındaki dillerle düşünülebilir kılar” sözü gerçekten doğru mu, emin değilim
Rust gibi dillerde DOM manipülasyonu yapmak için, sonuçta JavaScript tarafında çalıştırılacak çağrıları serileştiren binding’lere ihtiyaç olduğunu biliyorum
Wasm’ın mevcut biçimiyle hâlâ JavaScript’e bağlı değil mi diye düşünüyorum
Teorik olarak artık runtime’da DOM fonksiyonlarını alıp DOM nesne referanslarıyla çağırarak JavaScript’i bypass edip runtime’ı doğrudan çağırmak mümkün
Pratikte mümkün olup olmadığından emin değilim, ama GC en azından o noktaya gidebilmek için gereken ön mekanizmayı sağlıyor
Temelde SolidJS’e benziyor
#[component] fn App() -> impl IntoView { let (count, set_count) = create_signal(0); view! { "Click me: "{move || count()} } }JavaScript’e kıyasla Wasm boyutunda ek yük var, ama ciddi değil
wasm-optve Brotli sıkıştırmasından sonra bu sayaç uygulamasının Wasm bundle’ı 37KB idi; React ile benzer aralıkta ve çalıştıktan sonra çok daha hızlıDoğrudan DOM manipülasyonunu denemedim, ama tipik component’ler için iyi görünüyor
Örneğin Rust ile implemente edilmiş bir DOM’un, Rust ile yazılmış bir Wasm modülünden JavaScript çalıştırmadan kullanılabildiği bir biçim
Ancak DOM API’sinin JavaScript semantiğine göre tanımlanmış kısımları olduğu için zor; bu yüzden önce HTTP istekleri, TCP socket’leri, dosya sistemi erişimi gibi JavaScript mirası daha az olan şeylerle ilerliyorlar gibi görünüyor
“Ölü kod temizleme” terimi eskiden beri vardı; peki neden tree shaking ifadesi ortaya çıktı, merak ediyorum
“Tree shaking” ise çağrılmayan tüm modülleri ve fonksiyonları atan tüm program analizini ifade eder
Kavramsal olarak aynılar, ama derleyici yazarlarının çoğu zaman bunları ayrı ayrı uygulaması gerektiğinden iki adın olması işe yarıyor
Tree shaking’in çağrışımı kolay, “ölü kod temizleme”ye göre söylemesi daha rahat ve daha erişilebilir olduğu için daha popüler bir terim haline gelmiş gibi
Aslında hangi terimin daha önce kullanıldığını araştırınca, “dead-code elimination”ın en eski kullanımını 1973 tarihli bir makalede buldum: https://research-repository.st-andrews.ac.uk/bitstream/handle/10023/22636/NicholasAlexandrakisMScThesis1973_original_C.pdf?sequence=1
Google Scholar’da bilişim alanında “tree shaking” veya “tree shaker” kullanımına rastlamadım; çoğu narenciye ağaçları gibi ağaçlarla ilgili içeriklerdi
En erken tartışma gibi görünen şey comp.lang.lisp’teki bir gönderiydi: https://groups.google.com/forum/#!topic/comp.lang.lisp/pspFr1XByZk
Tree shaking metaforunun bazı meyve ağaçlarını hasat etme yöntemlerinden geldiğini düşünüyorum
Ağacı sallayınca olgun meyveler düşer
Ancak meyve hasadında düşeni isteriz; serileştirilmiş bir imajı kaydederken ise düşeni attığımız için çok iyi bir metafor sayılmaz
Meyveler daha narindir; çok uzağa düşerlerse berelenecekleri için çoğunlukla elle toplanırlar
Sallama süreci epey serttir, ama ağaca zarar vermez
Benim felsefem, JavaScript’te algoritma ve tasarım basitleştirmeleriyle boyut ve performansı sıkı biçimde optimize edip, kaba kuvvet hesaplama gerektiren kodlar için bundle boyutu ve CPU payı yaratmak.
ClubCompy projesinde local storage üzerinde bir FAT dosya sistemi uygulamak için Wasm kullanıyoruz; bunun hesaplama maliyetinin çok yüksek olduğu ortaya çıktı.
Bu yılın ilerleyen dönemlerinde piksel düzeyinde hassas sprite çarpışma algılamasını tekrar eklerken de Wasm kullanmayı planlıyorum.
İlk uygulama saf JavaScript’ti ve ekrandaki 256 sprite birbiriyle çarpışınca kare hızı 1 fps’nin altına düşüyordu.
Bunu worker thread’de pratikte bedavaya işleterek performans etkisi olmadan yapabileceğimizi düşünüyorum.
Çoğu 2D oyunun dikdörtgen çarpışma kutuları kullanmasının bir nedeni var: oyuncuların çarpışma olup olmayacağını öngörmesi daha kolaydır.
Piksel düzeyinde çarpışmada aynı hareket, animasyon döngüsünün fazına bağlı olarak bazen çarpışabilir, bazen çarpışmayabilir.
Daha önce her zaman işe yarayan bir hareket, animasyon zamanlaması kötü denk geldiği için başarısız olursa kötü hissettirir.
Üstelik piksel düzeyi her zaman gerçekçi de değildir; sprite’taki küçük ayrıntılar kumaş ya da saç gibi, gerçekte sert çarpışma yaratmayan unsurlar olabilir.
Sprite’lar çoğu zaman her karede birkaç piksel hareket ettiğinden, tek tek piksel çarpışmaları nesnelerin birbirinin içinden geçme olasılığını da artırır.
Basit ve öngörülebilir çarpışma algılama genelde en iyisidir.
1 fps, ancak 20 bin civarı çarpışmada görülmesi gereken bir kare hızıdır.
Piksel düzeyinde yapılacaksa, bellek payı yeterliyken basit bir yöntem olarak tüm oyun alanını offscreen canvas’a render edip her sprite’ı farklı renkte bir stencil olarak çizdikten sonra bunu kontrol edebilirsiniz.
Doğrusal zamanda çalışır ve ayrı bir bölümleme de gerektirmez.
Ancak anti-fingerprinting önlemleri canvas verisinin alt bitlerini gürültüyle değiştirebileceğinden, üst bitleri kullanmanız gerekebilir.
Bu yazı doğru şeyler söylüyor. Wasm’ın kod boyutu sorunu var
Tarayıcıda site başlamadan önce tüm kodun indirilmesi gerektiği için bu bir sorun; serverless mimarilerde de istemci beklerken kodun soğuk depolamadan belirli bir sunucuya gerektiğinde yüklenmesi nedeniyle sorun
Tree shaking yardımcı olabilir, ama muhtemelen yalnızca artımlı bir optimizasyon olarak kalır
Wasm programlarının temelde şişmesinin nedeni, her birinin kendi dil runtime’ını ve standart kütüphanesini bütünüyle getirmek zorunda olması
Buna karşılık JavaScript’te implementasyon ve temel kütüphaneler tarayıcı tarafından sağlanır
Bu yüzden tarayıcının tüm dil runtime’larını önceden içinde taşıyamayacağını düşünmek kolay; ama başka bir yaklaşım da değerlendirilmeli: paylaşımlı kütüphaneler ve dinamik linkleme
WebAssembly dinamik linklemeyi destekler ve birden fazla Wasm modülünü aynı anda yükleyip birbirlerini çağırmalarına izin verir
Ancak birçok Wasm toolchain’i bunu desteklemek istemiyor; bunun yerine tüm programı ve dil runtime’ını tek bir dev modül halinde statik linkleyecek şekilde tasarlanmış durumda
Pyodide (Wasm üzerinde CPython) bunun karşı örneği; şu anda dinamik linkleme düşünülerek tasarlanmış durumda
Cloudflare Workers’ın yakın zamanda Python’ı birinci sınıf destek olarak ekleyebilmesi de tam olarak bunun sayesinde oldu: https://blog.cloudflare.com/python-workers
Workers platformunun tamamının teknik lideri açısından bakınca, aynı makinede çalışan tüm Workers’lar derlenmiş tek bir Pyodide runtime’ını paylaşıyor; dolayısıyla her Worker için ayrı ayrı yüklemek gerekmiyor
Dinamik linkleme daha yaygın desteklenirse, tarayıcının popüler dil runtime’larını, hatta popüler kütüphaneleri önceden yüklediği ve bu runtime’a ihtiyaç duyan tüm web sayfalarının aynı salt okunur kod kopyasını paylaştığı bir yapı düşünülebilir
Bu runtime’lar hâlâ sandbox içinde çalışacağından tarayıcının onlara güvenmesi gerekmez; yalnızca sağlaması yeterli olur
Böylece tarayıcı bakımcıları dil implementasyonunu tamamen doğrulamak ya da dert etmek zorunda kalmadan JavaScript dışı dilleri “yerleşik” destekleyen tarayıcılar yapılabilir
Çok iyi bilmiyorum ama anladığım kadarıyla bu yöntem pek iyi işlemedi
Kütüphane sürümleri çok fazla olduğundan tekil sürümler gerçekte yaygın kullanılmıyordu; sonrasında gizlilik kaygıları nedeniyle tarayıcılar önbelleği siteye veya origin’e göre ayırma yönüne gitti
Amaç önbellekleme olmayabilir; yine de bu, teknik bir sorundan çok sosyal bir soruna yakın, zor bir mesele
Daha fazla dil runtime’ını tarayıcının varsayılan desteğine koymak yeni tarayıcıların giriş bariyerini yükseltir; ayrıca insanların istediği tüm kütüphane ve runtime’ları desteklemek de mümkün değil
Herkesin kendi getirmesine izin verip önbelleğe bel bağlanırsa, daha önce JavaScript kütüphanesi önbelleklemede yaşanan sorunlardan nasıl kaçınılacağı sorusu kalır
Örneğin bir Go paylaşımlı kütüphanesine runtime ve pek çok programın kullandığı standart kütüphanenin çekirdek bölümleri konabilir
Uygulama içi dinamik kütüphane desteği olmasa bile tüm Go programlarının boyutu küçültülebilir; dil runtime’ının da alan optimizasyonu için aşırı uğraşmasına gerek kalmaz
Çünkü zaten yüklüdür ve herhangi bir program tek bir fonksiyonunu bile kullanırsa boşa harcanmış alan sayılmaz
Bu, o dilde yazılmış programların boyut optimizasyonu maliyet modelini değiştirir
Dahil edilen standart kütüphane fonksiyonları, dili kullandığınız anda fiilen bedava hale gelir; dolayısıyla doğrudan kullanılabilir
Ancak yaygın kullanılan kütüphane ve framework’lerde sorun tekrar eder
Cloudflare üzerinde çalıştırırken Go için Cloudflare standart kütüphanesinin de paylaşılmasını istersiniz
Sorun şu ki diller runtime ile aynı hızda evrilmez
Birden fazla dil sürümü desteği sınırlanabilir ya da paylaşımlı kütüphaneler zamanla birikip uygulamalar arası paylaşım etkisini azaltabilir
JavaScript’in “seçenek yok” denebilecek bir sürüm modeli var; güçlü geriye dönük uyumluluk ve zaman zaman polyfill gerektiriyor
Bu başka diller için daha az uygun olabilir
Runtime gerçekten alanı azaltmak istiyorsa eklenti çeşitliliğini sınırlayabilir
Şikâyetler olsa da “JavaScript kullanmak zorundasın” yaklaşımı tarayıcıda oldukça iyi çalıştı
WebAssembly tabanlı dillerin bu kadar çeşitli olması gerekmeyebilir; Babil Kulesi durumundaki gibi çeşitliliğin de bir maliyeti var
Gerçek üretim bilgilerine dayanarak tree shaking yapılırsa, karmaşık statik analiz algoritmaları uygulamadan da ölü kodun veya neredeyse ölü kodun büyük kısmı kesilip atılabilir gibi görünüyor
Özellikle Hoot gibi bir bağlamda
appendChildgibi şeyler Scheme içinden çağrılan harici fonksiyonlardır: https://spritely.institute/news/building-interactive-web-pages-with-guile-hoot.htmlTeorik olarak herhangi bir Wasm ortamında JavaScript standart kütüphanesinin büyük kısmı bu şekilde kullanılabilir
Zig bu tür kullanım için mükemmel
Kişisel olarak Wasm dosya boyutu 100KB altında ise önemli bir unsur olmadığını, MB’ları aşarsa önemli hale geldiğini düşünüyorum
Yerleşik GC bazı uygulamalar için önemli, ama hepsi için değil; web uygulamalarını GC olmadan yapmak en iyisi
Wasm kullanan bir uygulamanın başarılı olmasındaki en önemli unsur hâlâ performans avantajı
Cloudflare Pages üzerinde bir Blazor uygulaması çalıştırıyorum; indirmeler hızlı ve performans iyi, ama yükleme süresi berbat
Bunun .NET ile çözülemeyeceğini düşünüyorum; temel sorun, nesne yönelimli dillerin tasarım gereği her şeyi birbirine dolaştırması gibi görünüyor
Ayrıca JavaScript’e aktarılan para miktarıyla rekabet etmek zor; JavaScript’te kopyala/yapıştır sanki bir dil özelliğiymiş gibi, hile kodu gibi
Üçüncü olarak, Blazor’da hâlâ JavaScript’e ve o taraftaki yetkinliğe ihtiyaç var; bence asıl sorun bu
Sorun, o dönemin dillerinin birçok kullanım senaryosunda reflection’a dayanmasıydı
.NET’in büyük bölümlerinde bu kullanım senaryolarını ortadan kaldırmak ve kullanılmayan kodun kaldırılması için güvenli olduğunu işaretlemek üzere insanlar yoğun biçimde çalışıyor
Reflection ile bir metodun çağrılma ihtimali varsa, neyin güvenle kaldırılabileceğini bilmek zor
Foo.Bar()çağrılan bir yer yok gibi görünse bile, biriReflection.getClass(someClass).runMethod(someVar)yapıyorsa ve bu değişkenler"Foo"ile"Bar"olarak ayarlanmışsa ne olacak?Örneğin Dart, önceden derlenmiş uygulamalarda reflection’a izin vermez; bu sayede kullanılmayan kod güvenle kaldırılabilir: https://docs.flutter.dev/resources/faq#does-flutter-come-with-a-reflection-mirrors-system
Dart nesne yönelimli bir dil, ancak çalışma zamanında kod üretimi ve çalışma zamanı reflection’ından kaçınıp derleme zamanında kod üretimini seçti
.NET de bu yöne gidiyor, ama bu bir gecede olacak iş değil
Ancak başkalarının da söylediği gibi, JavaScript dışı dillerin tarayıcının JavaScript çalışma zamanında bulunan standart kütüphane işlevlerinden kullandıkları bölümleri kendileriyle birlikte göndermek zorunda kalma sorunu da var
Mevcut paketleme modeli, Mono üzerinde Wasm paketlenmesinin sınırlamaları nedeniyle .NET trimming yeteneklerinden doğru dürüst yararlanamıyor
Gerçekte ne kadar iyi küçültebildiğini görmek için AOT ile sıradan bir uygulama derlemek daha iyi; o zaman küçük bir ikili dosya ortaya çıkıyor
dotnet/runtimelabiçindeki NativeAOT-LLVM Wasm hedefinin deneysel desteği çok daha küçük paket boyutu ve çok daha iyi performans sağlıyor; ancak hâlâdotnet/runtimealtında değil,dotnet/runtimelabaltında olduğundan ne zaman kullanılabilir olacağı belli değil