2 puan yazan GN⁺ 2024-04-15 | 1 yorum | WhatsApp'ta paylaş
  • 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 main derleme 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 main derleme 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
  • 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ı

    • display gibi 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
    • display her ş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, display iç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.bar ifadesinin ne anlama geldiği foo'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

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

 
GN⁺ 2024-04-15
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, talc gibi küçük bir allocator kullanmak ve bağımlılıkları azaltmak gibi bir yönetim gerekiyordu
    Yalnızca rand ve fxhash kullanıyorum; rand de muhtemelen kaldırılabilir, fxhash ise yalnızca oyun durumu hash’iyle senkron dışına çıkılıp çıkılmadığını kontrol etmek için kullanılıyor
    Generic instance türlerini de azalttım; zaten Vec varken Box<[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ı oldu
    Algoritmaları 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

    • Wasm’da kayan nokta tipleri yerleşik olduğu hâlde sabit nokta aritmetiğinin epey alan tasarrufu sağlaması ilginç
      İş 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
    • Ek olarak yardımcı olan şey binaryen’in wasm-opt aracını kullanmak
      Wasm 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
    • C++ ile derin pekiştirmeli öğrenme kütüphanesi RLtools’u geliştirdim ve Wasm örneği de yaptım (https://rl.tools); binary boyutunu hiç düşünmediğim hâlde tamamı yaklaşık 200–300 KB civarındaydı
      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
    • JavaScript’in tüm sayılar için double kullandığını sanıyordum; Wasm bu açıdan tamamen farklı mı, merak ediyorum
      Kayan noktadan kaçınıp sabit nokta aritmetiğiyle alan tasarrufu sağlamak nasıl mümkün oluyor, anlamak istiyorum
    • Gerçekte ne kadar alan tasarrufu sağlandığına dair rakamlar varsa görmek isterim
      Özellikle 6. maddede olduğu gibi Vec’i Box gibi kullanma yönteminin büyük bir tasarrufa yol açacağını sanmıyorum
  • Tree 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 main işlevi olan programları da gayet iyi üretiyor; çalışma zamanı sistemi yalnızca yığın izleme ve çöp toplama için gerektiğinden istenirse atlanabiliyor

    • Uygulama dağıtım aracı adı olarak Treeshaker muhtemelen Lisp’ten gelmiş olabilir
      Bulabildiğ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ı
    • Genel olarak doğru terim ölü kod kaldırma (dead-code elimination): https://en.wikipedia.org/wiki/Dead-code_elimination
      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
    • Yazılımda yanlış adlandırılmış çok şey var, ama kişisel olarak tree shaking adının harika olduğunu düşünüyorum
      İ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
    • Tree shaking’in bitki olan ağaca işaret ettiğini hiç düşünmedim; veri yapısı olarak tree aklıma geldi
      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
    • Ortada çok miktarda kod var ve bu bir tree
      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

    • WasmGC’nin Rust’a da yardımcı olup olmayacağını merak ediyorum
      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-opt kullanır ve Brotli ile sıkıştırırsanız, indirme boyutu 100 KB’nin altında kalacak şekilde muazzam miktarda kod sığdırabilirsiniz
      100 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

    • Önceki “GC ile birlikteyse” koşulunu dahil etmek önemli
      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
    • Yakın zamanda Rust için Leptos’u denedim; Wasm ile web frontend’i oluşturmaya yarayan çok hızlı bir framework ve dürüst olmak gerekirse iyi görünü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-opt ve 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
    • Anladığım kadarıyla WASI’nin amaçlarından biri DOM’a doğrudan giden bir API sağlamak
      Ö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

    • “Ölü kod temizleme” genelde bir fonksiyon içinde asla ulaşılamayan kod parçalarını atan küçük ölçekli bir derleyici optimizasyonunu ifade eder
      “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
    • Hangi kelimenin hayatta kalıp ana akım olacağı, ne kadar eski olduğuyla pek ilgili değil
      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
    • Eğer gerçekten Lisp bağlamından geldiyse, bunun nedeni yalnızca çalıştırılabilir kodun değil, gereksiz veri ve metaverinin kaldırılmasının da aynı ağırlıkta ele alınması olabilir
    • Ölü kod temizleme ifadesi çok daha iyi
    • Daha canlı bir ifade ve “ölü kod temizleme”ye göre daha konuşma dili gibi geliyor; yayılmasının nedenini anlamak zor değil
  • 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

    • Genelde sallayarak hasat edilen şey kuruyemişlerdir
      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.

    • Piksel düzeyinde çarpışma algılama ilk duyulduğunda kulağa iyi gelebilir, ancak gerçekte oynarken rahatsız edici hissettirebilir.
      Ç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.
    • 256 çarpışmada 1 fps görüyorsanız darboğaz programlama dili değil, algoritmadır.
      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

    • Tarayıcının birden çok popüler runtime ve kütüphaneyi önceden yükleyip web sayfalarının bunları paylaşması fikri, eskiden yaygın JavaScript framework’lerini ortak bir CDN’den sunarsak çoğu tarayıcı önbelleğinde zaten bulunur, indirme gerekmez ve framework boyutu pek önemli olmaz düşüncesine benziyor
      Ç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
    • Sınırlı dinamik linkleme desteği bile epey büyük etki yaratabilir gibi görünüyor
      Ö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
    • Profil tabanlı tree shaking ile uygulamanın ihtiyaç duyduğu kodu içeren küçük modüller oluşturup, daha az kullanılan özellikleri dinamik linkleme ile gerektiğinde getirmek mümkün mü merak ediyorum
      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
    • Tarayıcının popüler runtime’ları önceden indirdiği bir yapı, sonunda tarayıcıya yüzlerce runtime sürümünün indirilmesi ve her bir binary’nin yalnızca tek bir sitede kullanılarak önbelleği doldurması riski taşır
    • Genel olarak katılıyorum, ama Wasm’da saf hesaplama dışı işler yapmak için tarayıcıya yerleşik JavaScript’i epey kullandığınızı da belirtmek isterim
      Özellikle Hoot gibi bir bağlamda appendChild gibi şeyler Scheme içinden çağrılan harici fonksiyonlardır: https://spritely.institute/news/building-interactive-web-pages-with-guile-hoot.html
      Teorik 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

    • Bu nesne yönelimliliğin sorunu değil
      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, biri Reflection.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
    • Blazor bu açıdan pek iyi değil
      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/runtimelab iç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/runtime altında değil, dotnet/runtimelab altında olduğundan ne zaman kullanılabilir olacağı belli değil