Uygulamanızın JavaScript'indeki aktif yolu WebAssembly ile değiştirme

Sürekli hızlı ilerliyor

Önceki makalelerimde, WebAssembly'nin C/C++ kitaplık ekosistemini web'e nasıl getirdiğinizden bahsetmiştim. Uygulama içi C/C++ kitaplıklarını kapsamlı bir şekilde kullanır. squoosh, haline getirilmiş çeşitli codec'ler ile sıkıştırmanıza olanak tanıyan bir web C++'tan WebAssembly'ye derlenir.

WebAssembly, depolanan bayt kodunu çalıştıran alt düzey bir sanal makinedir. .wasm dosyada. Bu bayt kodu, çok basit bir şekilde yazılmış ve daha hızlı bir şekilde derlenip ana makine sistemi için optimize edilebilmesini sağlar. JavaScript bunları yapabilir. WebAssembly, aklınızdan çıkarmayın ve yerleştirmeyi unutmayın.

Deneyimlerime göre, web'deki performans sorunlarının çoğu, düzen ve aşırı miktarda boyama da var. Ancak ara sıra bir uygulamanın maliyetli ve fazla zaman alan bir görevdir. WebAssembly size burayı tıklayın.

Popüler Yol

Squoosh'ta, bir resim arabelleğini 90 derecenin katları kadar döndüren bir JavaScript işlevi yazdık. OffscreenCanvas bu amaç için ideal olsa da hedeflediğimiz tarayıcılarda desteklenmemektedir ve Chrome'da biraz hatalı.

Bu işlev, bir giriş resminin her pikseli üzerinde iterasyon gerçekleştirir ve döndürmeyi gerçekleştirmek için pikseli çıkış resmindeki farklı bir konuma kopyalar. 4.094 piksel için 4096 piksel (16 megapiksel) bir görüntünün 16 milyonun üzerinde iterasyonu Buna "kısa yol" adını veriyoruz. Proje oldukça büyük olsa da test ettiğimiz her üç tarayıcıdan ikisi, işlemi 2 saniyede bitirir saniye veya daha kısa. Bu tür etkileşimler için kabul edilebilir bir süre.

for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    const in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

Ancak bir tarayıcı 8 saniyeden uzun sürüyor. Tarayıcıların JavaScript'i optimize etme şekli gerçekten karmaşıktır ve farklı motorlar farklı şeyler için optimizasyon yapar. Bazıları ham yürütme için, bazıları DOM ile etkileşim için optimizasyon yapar. Bu durumda, bir tarayıcıda optimize edilmemiş bir yola ulaştık.

Öte yandan WebAssembly, tamamen ham yürütme hızıyla geliştirilmiştir. ODK Bunun gibi kodların tarayıcılarda hızlı, tahmin edilebilir performans göstermesini istiyorsak, WebAssembly size yardımcı olabilir.

Tahmin edilebilir performans için WebAssembly

Genel olarak, JavaScript ve WebAssembly aynı en yüksek performansı elde edebilir. Bununla birlikte, JavaScript'te bu performansa yalnızca "hızlı yolda", ve bu "hızlı yolda" kalmak genellikle zordur. Projenin yaşam döngüsünün WebAssembly, farklı tarayıcılarda bile tahmin edilebilir performans sunar. Katı Yazma ve alt düzey mimari, derleyicinin daha güçlü WebAssembly kodunun yalnızca bir kez optimize edilmesini ve her zaman "hızlı yolu" kullanın.

WebAssembly için yazma

Daha önce C/C++ kitaplıklarını alıp yardımcı olmasını sağlar. Kütüphanelerin koduna dokunmadık, Chrome Web Mağazası'ndaki tarayıcı ile bir köprü oluşturmak için ve kütüphane. Bu seferki motivasyonumuz farklı: WebAssembly'in avantajlarından yararlanabilmek için sıfırdan WebAssembly'i göz önünde bulundurarak bir şey yazmak istiyoruz.

WebAssembly mimarisi

WebAssembly için yazarken, kampanya hakkında biraz daha bilgi sahibi olmanız aslında ne olduğunu öğrenin.

WebAssembly.org'dan alıntı yapmak için:

WebAssembly'de C veya Rust kodu parçası derlediğinizde bir .wasm alırsınız dosyasını seçin. Bu beyan aşağıdakilerin bir listesinden oluşur: "içe aktarmalar" dışa aktarımların bir listesidir. Bu dışa aktarımların ana makineye (işlevler, sabit değerler, bellek parçaları) ve fonksiyonların gerçek ikili talimatlarıdır.

Bunu inceleyene kadar fark etmediğiniz bir şeyi fark ettim: WebAssembly, "yığın tabanlı bir sanal makine" parçasında depolanmaz kullanılan belirli bir bellek birimi anlamına gelir. Yığın tamamen sanal makine içindedir ve web geliştiricileri tarafından erişilemez (Geliştirici Araçları hariç). Bu nedenle, hiç ek belleğe ihtiyaç duymayan ve yalnızca sanal makine içi yığını kullanan WebAssembly modülleri yazılabilir.

Bizim durumumuzda, resmimizin piksellerine keyfi erişim izni vermek ve bu resmin döndürülmüş bir sürümünü oluşturmak için biraz ek bellek kullanmamız gerekir. WebAssembly.Memory bunun içindir.

Bellek yönetimi

Genellikle, ek bellek kullandıktan sonra bu belleği bir şekilde yönetmeniz gerekir. Belleğin hangi bölümleri kullanılıyor? Hangileri ücretsiz? Örneğin, C'de n art arda baytlık bir bellek alanı bulan malloc(n) işlevi vardır. Bu tür işlevlere "dağıtıcılar" da denir. Kullanımdaki toplayıcının uygulaması elbette webAssembly modülünü kullanabilir ve dosyanızın boyutunu büyütebilirsiniz. Bu boyut ve performans bağlı olarak, veri feed'inizde belirtilen bu bellek yönetimi işlevleri birçok dilin birden fazla uygulama seçeneği sunmasının nedeni, kullanılan algoritmanın ("dmalloc", "emmalloc", "wee_alloc" vb.) arasından seçim yapabilirsiniz.

Örneğimizde, giriş resminin boyutlarını biliyoruz (dolayısıyla da çıkış resminin boyutları) çalıştıracağız. Burada bir fırsat olduğunu görmüş olduk: Geleneksel olarak, giriş resminin RGBA tamponu parametresini WebAssembly işlevine ekleyebilir ve döndürülen resmi, değer. Bu döndürülen değeri oluşturmak için ayırıcıdan yararlanmamız gerekir. Ama gerekli toplam bellek miktarını bildiğimiz için (giriş boyutunun iki katı bir kez giriş ve bir kez çıkış için olmak üzere), giriş resmini JavaScript kullanan WebAssembly belleği kullanıyorsanız web sayfası oluşturmak için WebAssembly modülünü 2. döndürülmüş resim ve ardından, sonucu tekrar okumak için JavaScript'i kullanın. Bellek yönetimi kullanmadan da bu işlemi yapabiliriz.

Seçenek çok

Orijinal JavaScript işlevine baktıysanız olduğunu fark ederseniz, bunun tamamen sayısal bir işlem JavaScript'e özel API'ler içermeyen kod. Bu nedenle, bu kodu herhangi bir dile taşımak oldukça kolaydır. WebAssembly'e derlenen 3 farklı dili değerlendirdik: C/C++, Rust ve AssemblyScript. Tek soru tüm diller için cevap vermemiz gereken soru şudur: Ham belleğe nasıl erişiriz? ne kadar iyi performans gösterir?

C ve Emscripten

Emscripten, WebAssembly hedefi için bir C derleyicisidir. Emscripten'in hedefi GCC veya clang gibi tanınmış C derleyicileri için bir alternatif işlevi görür ve çoğunlukla flag'lerle uyumludur. Mevcut C ve C++ kodlarını WebAssembly'e derlemeyi mümkün olduğunca kolaylaştırmak isteyen Emscripten, bu özelliği misyonunun temel bir parçası olarak görmektedir.

Ham belleğe erişmek C'nin doğasında vardır ve bu süreç için işaretçiler neden:

uint8_t* ptr = (uint8_t*)0x124;
ptr[0] = 0xFF;

Burada 0x124 sayısını imzasız, 8 bitlik bir işaretçiye dönüştürüyoruz. tam sayılar (veya baytlar). Bu, ptr değişkenini etkili bir şekilde diziye dönüştürür. ile başlayan bir 0x124 bellek adresi oluştururuz. Böylece okuma ve yazma işlemleri için baytlara ayrı ayrı erişebiliriz. Bizim durumumuzda, döndürmeyi gerçekleştirmek için yeniden sıralamak istediğimiz bir resmin RGBA arabelleğine bakıyoruz. Bir pikseli taşımak için tek seferde art arda 4 bayt taşımamız gerekir (her kanal için bir bayt: R, G, B ve A). Bunu kolaylaştırmak için 32 bit tam sayılar dizisidir. Kural olarak, giriş resmimiz 4. adreste başlar ve çıkış resmimiz doğrudan giriş resminin bitiminden sonra başlar:

int bpp = 4;
int imageSize = inputWidth * inputHeight * bpp;
uint32_t* inBuffer = (uint32_t*) 4;
uint32_t* outBuffer = (uint32_t*) (inBuffer + imageSize);

for (int d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (int d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    int in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

JavaScript işlevinin tamamını C'ye taşıdıktan sonra emcc ile C dosyasını derleyebiliriz:

$ emcc -O3 -s ALLOW_MEMORY_GROWTH=1 -o c.js rotate.c

Her zaman olduğu gibi emscripten, c.js adlı bir yapıştırma kodu dosyası ve c.wasm adlı bir wasm modülü oluşturur. wasm modülünün yalnızca yaklaşık 260 bayta sıkıştırıldığını, yapıştırma kodunun ise sıkıştırıldıktan sonra yaklaşık 3,5 KB olduğunu unutmayın. Biraz uğraştıktan sonra, yapıştırıcı kodunu kaldırıp WebAssembly modüllerini standart API'lerle örnekleyebildik. Herhangi bir şey kullanmadığınız sürece Emscripten'de bu durumla sık sık karşılaşılır C standart kitaplığından alınır.

Rust

Rust, zengin bir tür sistemine, çalışma zamanına sahip olmayan ve bellek güvenliği ile iş parçacığı güvenliğini garanti eden bir sahiplik modeline sahip yeni ve modern bir programlama dilidir. Rust, temel bir özellik olarak WebAssembly'i de destekler. Rust ekibi, WebAssembly ekosistemine birçok mükemmel araç katkısında bulunmuştur.

Bu araçlardan biri, rustwasm çalışma grubunun wasm-pack aracıdır. wasm-pack. kodunuzu alıp web'de çalışan bir modüle dönüştürür ilk kullanım olanağı sunar. wasm-pack son derece kullanışlı bir deneyim sunuyor, ancak şu anda yalnızca Rust'ta kullanılabilir. Grup diğer WebAssembly hedefleme dilleri için destek sunmayı değerlendiriyoruz.

Rust'ta dilimler, C'de bulunan dizilerdir. Tıpkı C'de olduğu gibi, başlangıç adreslerimizi kullanan dilimler. Bu, bellek güvenliği modeline aykırıdır bu, Rust'ın zorunlu kıldığı bir araç haline geldi. Yani, unsafe anahtar kelimesini kullanmamız gerekiyor. bu modele uymayan kod yazmamızı sağlar.

let imageSize = (inputWidth * inputHeight) as usize;
let inBuffer: &mut [u32];
let outBuffer: &mut [u32];
unsafe {
    inBuffer = slice::from_raw_parts_mut::<u32>(4 as *mut u32, imageSize);
    outBuffer = slice::from_raw_parts_mut::<u32>((imageSize * 4 + 4) as *mut u32, imageSize);
}

for d2 in 0..d2Limit {
    for d1 in 0..d1Limit {
    let in_idx = (d1Start + d1 * d1Advance) * d1Multiplier + (d2Start + d2 * d2Advance) * d2Multiplier;
    outBuffer[i as usize] = inBuffer[in_idx as usize];
    i += 1;
    }
}

Rust dosyalarını şunu kullanarak derleme:

$ wasm-pack build

yaklaşık 100 baytlık bir yapıştırma kodu içeren 7,6 KB'lık bir wasm modülü oluşturur (her ikisi de gzip'den sonra).

AssemblyScript

AssemblyScript, oldukça yeni bir projeniz var. Ancak, herhangi bir TypeScript'i tüketmeyeceğini unutmayın. AssemblyScript, TypeScript ile aynı söz dizimini kullanır ancak standart kitaplığı kendi kitaplığıyla değiştirir. Kullandıkları standart kitaplıktaki WebAssembly. Bu, yalanladığınız herhangi bir TypeScript'i derleyemeyeceğiniz anlamına gelir. webAssembly'yi de ekler, ancak bu anlamına gelir programlama dilini kullanabilirsiniz.

    for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
      for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
        let in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
        store<u32>(offset + i * 4 + 4, load<u32>(in_idx * 4 + 4));
        i += 1;
      }
    }

rotate() işlevimizin küçük tür yüzeyi göz önüne alındığında, bu kodu AssemblyScript'e taşımak oldukça kolaydı. load<T>(ptr: usize) ve store<T>(ptr: usize, value: T) işlevleri, ham belleğe erişmek için AssemblyScript tarafından sağlanır. AssemblyScript dosyamızı derlemek için AssemblyScript/assemblyscript npm paketini yüklememiz ve

$ asc rotate.ts -b assemblyscript.wasm --validate -O3

AssemblyScript bize yaklaşık 300 Bayt wasm modülü sağlar ancak yapışkan kodu yoktur. Modül yalnızca standart WebAssembly API'leriyle çalışır.

WebAssembly Adli Bilimleri

Rust'ın 7,6 KB'lık boyutu, diğer 2 dile kıyasla şaşırtıcı derecede büyük. WebAssembly ekosisteminde, WebAssembly dosyalarınızı (oluşturuldukları dilden bağımsız olarak) analiz etmenize, neler olduğunu anlamanıza ve durumunuzu iyileştirmenize yardımcı olabilecek birkaç araç vardır.

Twiggy

Twiggy, Rust'un WebAssembly ekibi tarafından geliştirilen ve WebAssembly modülünden bir dizi yararlı veri çıkaran bir araçtır. Rust'a özgü olmayan bu araç, modülün çağrı grafiği gibi öğeleri incelemenize, kullanılmayan veya gereksiz bölümleri belirlemenize ve modülünüzün toplam dosya boyutuna hangi bölümlerin katkıda bulunduğunu anlamanıza olanak tanır. İlgili içeriği oluşturmak için kullanılan ikincisi Twiggy'nin top komutuyla yapılabilir:

$ twiggy top rotate_bg.wasm
Twiggy kurulum ekran görüntüsü

Bu örnekte, dosya boyutumuzun büyük bir kısmının ayırıcıdır. Kodumuz dinamik ayırmaları kullanmadığı için bu şaşırtıcıydı. Etki yaratan bir diğer faktör de "işlev adları"dır. bölüm oluşturabilirsiniz.

wasm şeridi

wasm-strip, WebAssembly İkili Program Araç Seti'ndeki bir araçtır veya kısaca wabt'tır. Bir WebAssembly modüllerini incelemenize ve değiştirmenize olanak tanıyan iki araçtan yararlanabilirsiniz. wasm2wat, bir ikilik wasm modülünü insanlar tarafından okunabilir bir biçime dönüştüren bir disassembler'dır. Wabt, bu insan tarafından okunabilir biçimi tekrar ikili wasm modülüne dönüştürmenize olanak tanıyan wat2wasm değerini de içerir. WebAssembly dosyalarımızı incelemek için bu iki tamamlayıcı aracı kullandık ancak en faydalı olanı wasm-strip bulduk. wasm-strip gereksiz bölümleri kaldırır ve meta veriler aşağıda verilmiştir:

$ wasm-strip rotate_bg.wasm

Bu işlem, pas modülünün dosya boyutunu 7,5 KB'tan 6,6 KB'a (gzip'ten sonra) düşürür.

wasm-opt

wasm-opt, Binaryen tarafından sunulan bir araçtır. Bir WebAssembly modülü gerekir ve modülü hem boyut hem de bayt koduna göre performans göstermeyi sağlar. Emscripten gibi bazı araçlar bazıları ise yoktur. Genellikle ilerlemeye devam etmek için ek bayt olarak değerlendirebilirsiniz.

wasm-opt -O3 -o rotate_bg_opt.wasm rotate_bg.wasm

wasm-opt ile bir dizi bayt daha ayıklayabiliriz. Böylece, toplamda gzip'ten sonra 6,2 KB.

#![no_std]

Biraz danışmanlık ve araştırmadan sonra, bunu yapmadan önce Rust kodumuzu Rust'ın standart kitaplığındaki #![no_std] özelliğini kullanabilirsiniz. Bu, dinamik bellek ayırmalarını da tamamen devre dışı bırakarak ayırıcı kodunu modülümüzün içinden ayırabiliriz. Bu Rust dosyasını aşağıdakilerle derleme

$ rustc --target=wasm32-unknown-unknown -C opt-level=3 -o rust.wasm rotate.rs

wasm-opt, wasm-strip ve gzip'ten sonra 1,6 KB'lık wasm modülü sağladı. Ancak C ve AssemblyScript tarafından oluşturulan modüllerden daha büyük olduğundan, kabul edilir.

Performans

Sadece dosya boyutunu temel alarak bir sonuca ulaşmaya geçmeden önce, optimize edebilirsiniz. Peki performansı nasıl ölçtük ve sonuçlar ne oldu?

Karşılaştırma yapma

WebAssembly düşük seviyeli bir bayt kodu biçimi olsa da, kullanarak ana makineye özel makine kodu oluşturabilirsiniz. JavaScript gibi derleyici de birden fazla aşamada çalışır. Basitçe ifade etmek gerekirse: İlk aşama, daha hızlı derlemeye başlar ancak daha yavaş kod üretme eğilimindedir. Modül çalışmaya başladıktan sonra tarayıcı, sık kullanılan bölümleri gözlemler ve bunları daha optimize edilmiş ancak daha yavaş bir derleyiciden gönderir.

Kullanım alanımız, bir resmi döndürme kodunun kullanılması açısından ilginçtir. bir kez, belki iki kez. Bu nedenle, çoğu durumda optimize edici derleyicinin avantajlarından yararlanamayız. Karşılaştırma yaparken bunu göz önünde bulundurmanız önemlidir. WebAssembly modüllerimizi bir döngüde 10.000 kez çalıştırmak gerçekçi olmayan sonuçlara yol açabilir. Gerçekçi sayılara ulaşmak için modülü bir kez çalıştırmalı ve o tek koşuda elde edilen sayılara dayanarak kararlar vereceksiniz.

Performans karşılaştırması

Dil başına hız karşılaştırması
Tarayıcı başına hız karşılaştırması

Bu iki grafik, aynı verilerin farklı görünümleridir. İlk grafikte karşılaştırma yaptık. İkinci grafikte, kullanılan dil başına karşılaştırma yaptık. Lütfen logaritmik bir zaman ölçeği seçtiğimi unutmayın. Projedeki her iletişimin karşılaştırma için kullanılan cihaz aynı 16 megapiksellik makinede çalıştırılamayan bir tarayıcı bulunuyor.

Bu grafikleri çok fazla analiz etmeden, orijinal performans sorunumuzu çözdüğümüz açıktır: Tüm WebAssembly modülleri yaklaşık 500 ms veya daha kısa sürede çalışır. Bu, başlangıçta belirttiğimizi doğrular: WebAssembly, tahmin edilebilir performans sağlar. Hangi dili seçersek seçelim, tarayıcılar arasındaki farklılık ve dil kullanımı asgari düzeydedir. Daha doğrusu: Tüm tarayıcılarda JavaScript'in standart sapması yaklaşık 400 ms, tüm tarayıcılarda tüm WebAssembly modüllerimizin standart sapması ise yaklaşık 80 ms'dir.

Yapılması gerekenler

Diğer bir metrik de her şeyi oluşturup bunları bir araya getirmek için WebAssembly modülümüzü squoosh haline getiriyoruz. Çalışmaya sayısal bir değer atamak zor olduğundan herhangi bir grafik oluşturmayacağım ancak vurgulamak istediğim birkaç nokta var:

AssemblyScript sorunsuzdu. Yalnızca bu özellik için TypeScript'i kullanmak meslektaşlarım için kod incelemesini çok kolay hale getiren bir araç olan WebAssembly yazma yapışkansız WebAssembly modüllerini sağlar. bazı yolları da görmüştük. TypeScript ekosistemindeki daha hoş ve tslint gibi araçlar, muhtemelen işe yarar.

wasm-pack ile birlikte Rust da son derece kullanışlıdır ancak özellikle bağlama ve bellek yönetiminin gerekli olduğu daha büyük WebAssembly projelerinde daha iyi performans gösterir. Rekabetçi bir dosya boyutuna ulaşmak için ideal yoldan biraz sapmamız gerekti.

C ve Emscripten, kutudan çıkar çıkmaz çok küçük ve yüksek performanslı bir WebAssembly modülü oluşturdu ancak yapıştırıcı koda atlayıp gerekli olan minimum boyuta indirme cesareti olmadığı için toplam boyut (WebAssembly modülü + yapıştırıcı kod) oldukça büyük oluyor.

Sonuç

Peki bir JS etkin yolunuz varsa ve bunu oluşturmak istiyorsanız veya WebAssembly ile daha hızlı veya tutarlı bir şekilde çalışmasını sağlar. Performansta her zamanki gibi cevap: Duruma göre değişir. Peki ne gönderdik?

Karşılaştırma grafiği

Kullandığımız farklı dillerin modül boyutu/performans dengesi açısından karşılaştırıldığında en iyi seçenek C veya AssemblyScript gibi görünüyor. Rust'u kullanıma sunmaya karar verdik. Bu kararın birden fazla nedeni vardır: Squoosh'ta şu ana kadar kullanıma sunulan tüm codec'ler Emscripten kullanılarak derlenmiştir. WebAssembly ekosistemi hakkındaki bilgilerimizi genişletmek ve üretimde farklı bir dil kullanmak istedik. AssemblyScript güçlü bir alternatiftir ancak proje nispeten yenidir ve derleyici, Rust derleyici kadar olgun değildir.

Rust ve diğer dillerin dosya boyutu arasındaki fark oldukça büyük görünüyorsa aslında bu, o kadar da büyük bir şey değildir: 2G üzerinden bile 500 B veya 1,6 KB yüklemek saniyenin 1/10'undan kısa sürer. Rust'ın yakında modül boyutu açısından bu açığı kapatacağını umuyoruz.

Çalışma zamanı performansı açısından bakıldığında, Rust diğer tarayıcılarda AssemblyScript. Özellikle de büyük projelerde Rust'ın ve manuel kod optimizasyonlarına gerek kalmadan daha hızlı kod üretmenizi sağlar. Ama bu sizi en rahat ettiğiniz şeyi kullanmaktan alıkoymaz.

Tüm bunlara rağmen AssemblyScript harika bir keşif oldu. Web'e izin verir geliştiricilerin yeni bir bilgi edinmek zorunda kalmadan WebAssembly modülleri dili'ne dokunun. AssemblyScript ekibi son derece duyarlıydı ve aktif bir şekilde geliştirmeye karar veriyor. Gelecekte AssemblyScript'i kesinlikle takip edeceğiz.

Güncelleme: Pas

Bu makale yayınlandıktan sonra Rust ekibinden Nick Fitzgerald, dosya boyutunu optimize etmeyle ilgili bir bölüm içeren mükemmel Rust Wasm kitaplarını bize önerdi. Buradaki talimatları uygulamak (özellikle de bağlantı zamanında optimizasyonları ve manuel panik yönetimi özelliklerini etkinleştirmek) "normal" Rust kodu yazmamıza ve dosya boyutunu şişirmeden Cargo (Rust'un npm) kullanmaya geri dönmemize olanak tanıdı. Rust modülü, gzip'ten sonra 370 B olur. Ayrıntılar için lütfen Squoosh'ta açtığım PR'ye göz atın.

Bu yolculukta bize yardımcı oldukları için Ashley Williams, Steve Klabnik, Nick Fitzgerald ve Max Graey'e özel teşekkürlerimizi sunuyoruz.