Yayınlanma tarihi: 30 Ocak 2025
Web'deki birçok WebAssembly uygulaması, yerel uygulamalarla aynı şekilde çoklu iş parçacıklılıktan yararlanır. Birden fazla iş parçacığı, paralel olarak daha fazla çalışma yapılmasına olanak tanır ve gecikmeli sorunları önlemek için ağır işleri ana iş parçacığından uzaklaştırır. Yakın zamana kadar, bu tür çok iş parçacıklı uygulamalarda, ayırma ve G/Ç ile ilgili bazı yaygın sorunlar yaşanıyordu. Neyse ki Emscripten'deki son özellikler bu sorunlar konusunda size çok yardımcı olabilir. Bu kılavuzda, bu özelliklerin bazı durumlarda hızın 10 kat veya daha fazla artmasına nasıl yol açabileceği gösterilmektedir.
Ölçeklendirme
Aşağıdaki grafikte, saf matematik iş yükünde verimli çok iş parçacıklı ölçeklendirme gösterilmektedir (bu makalede kullanacağımız karşılaştırmadan alınmıştır):
Bu, her CPU çekirdeğinin tek başına yapabileceği saf hesaplamayı ölçer. Bu nedenle, daha fazla çekirdekle performans artar. Daha hızlı performans gösteren bu azalan çizgi, iyi ölçeklendirmenin tam olarak nasıl göründüğünü gösterir. Ayrıca, web platformunun paralellik için temel olarak web işçilerini kullanmasına, gerçek yerel kod yerine Wasm'i kullanmasına ve daha az optimal görünebilecek diğer ayrıntılara rağmen çok iş parçacıklı yerel kodu çok iyi şekilde yürütebildiğini gösteriyor.
Yığın yönetimi: malloc
/free
malloc
ve free
, tamamen statik olmayan veya yığın üzerinde olmayan tüm belleği yönetmek için kullanılan, tüm doğrusal bellek dillerindeki (ör. C, C++, Rust ve Zig) kritik standart kitaplık işlevleridir. Emscripten varsayılan olarak dlmalloc
kullanır. Bu, kompakt ancak verimli bir uygulamadır (daha da kompakt ancak bazı durumlarda daha yavaş olan emmalloc
'i de destekler). Ancak dlmalloc
, her malloc
/free
için kilit aldığından (tek bir genel ayırıcı olduğundan) çok iş parçacıklı performansı sınırlıdır. Bu nedenle, aynı anda birçok iş parçacığında çok sayıda tahsisatınız varsa anlaşmazlık ve yavaşlamayla karşılaşabilirsiniz. İnanılmaz derecede malloc
yoğun bir karşılaştırma testi çalıştırdığınızda ne olur?
Daha fazla çekirdekle performans yalnızca iyileşmez, her iş parçacığı malloc
kilidi için uzun süre beklemek zorunda kaldığından giderek daha da kötüye gider. Bu, mümkün olan en kötü durumdur ancak yeterli tahsis varsa gerçek iş yüklerinde de gerçekleşebilir.
mimalloc
dlmalloc
'ün çok iş parçacıklı olarak optimize edilmiş sürümleri vardır. Örneğin, ptmalloc3
, iş parçacığı başına ayrı bir ayırıcı örneği uygulayarak anlaşmazlıkları önler.
jemalloc
ve tcmalloc
gibi çoklu iş parçacığı optimizasyonlarına sahip başka dağıtıcılar da vardır. Emscripten, Microsoft'un çok iyi taşınabilirlik ve performansa sahip, güzel tasarlanmış bir ayırıcı olan son mimalloc
projesine odaklanmaya karar verdi. Aşağıdaki şekilde kullanın:
emcc -sMALLOC=mimalloc
mimalloc
kullanan malloc
karşılaştırması için sonuçlar aşağıda verilmiştir:
Mükemmel! Artık performans verimli bir şekilde ölçeklenip her çekirdekle daha da hızlanıyor.
Son iki grafikteki tek çekirdek performansına ait verilere dikkatlice bakarsanız dlmalloc
için 2.660 ms, mimalloc
için ise yalnızca 1.466 ms olduğunu görürsünüz. Bu da hızda neredeyse 2 kat artış anlamına gelir. Bu, tek iş parçacıklı bir uygulamada bile mimalloc
'ün daha gelişmiş optimizasyonlarından yararlanabileceğinizi gösterir. Ancak bu, kod boyutu ve bellek kullanımı açısından bir maliyete neden olur (bu nedenle dlmalloc
varsayılan olarak kalır).
Dosyalar ve G/Ç
Birçok uygulamanın çeşitli nedenlerle dosya kullanması gerekir. Örneğin, bir oyunda seviyeleri veya bir resim düzenleyicide yazı tiplerini yüklemek için. printf
gibi bir işlem bile, stdout
dosyasına veri yazarak yazdırdığı için arka planda dosya sistemini kullanır.
Tek iş parçacıklı uygulamalarda bu genellikle sorun teşkil etmez ve ihtiyacınız olan tek şey printf
ise Emscripten, tam dosya sistemi desteğini otomatik olarak bağlamaz. Ancak dosya kullanıyorsanız dosya erişimi, iş parçacıkları arasında senkronize edilmesi gerektiğinden çok iş parçacıklı dosya sistemi erişimi zordur. Emscripten'deki orijinal dosya sistemi uygulaması, JavaScript'te uygulandığı için "JS FS" olarak adlandırılır ve dosya sistemini yalnızca ana iş parçacığında uygulamak gibi basit bir model kullanır. Başka bir iş parçacığı bir dosyaya erişmek istediğinde, ana iş parçacığına istek gönderir. Diğer iş parçacığı, iş parçacığı arası bir istekte kilitleniyordur. Bu istek, sonunda ana iş parçacığı tarafından ele alınır.
Bu basit model, dosyalara yalnızca ana mesaj dizisi erişiyorsa (sık karşılaşılan bir durumdur) idealdir. Ancak diğer iş parçacıkları okuma ve yazma işlemi yaparsa sorunlar ortaya çıkar. İlk olarak, ana iş parçacığı diğer iş parçacıkları için iş yapmaya başlar ve kullanıcı tarafından görülebilen gecikmeye neden olur. Ardından, arka plandaki iş parçacıkları, ihtiyaç duydukları işi yapmak için ana iş parçacığının boş olmasını bekler. Bu nedenle işler yavaşlar (veya daha da kötüsü, ana iş parçacığı şu anda bu iş parçacığını bekliyorsa kilitlenmeyle karşılaşabilirsiniz).
WasmFS
Emscripten'de bu sorunu düzeltmek için yeni bir dosya sistemi uygulaması olan WasmFS bulunur. WasmFS, JavaScript'te yazılmış orijinal dosya sisteminin aksine C++'ta yazılmış ve Wasm'e derlenmiştir. WasmFS, dosyaları tüm iş parçacıkları arasında paylaşılan Wasm doğrusal belleğinde saklayarak birden fazla iş parçacığının dosya sistemine erişimini minimum düzeyde ek yük oluşturacak şekilde destekler. Artık tüm iş parçacıkları eşit performansla dosya G/Ç işlemi yapabilir ve çoğu zaman birbirlerini engellemekten kaçınabilir.
Basit bir dosya sistemi karşılaştırması, WasmFS'nin eski JS FS'ye kıyasla ne kadar büyük bir avantaja sahip olduğunu gösterir.
Bu karşılaştırmada, dosya sistemi kodunun doğrudan ana iş parçacığında çalıştırılması tek bir pthread üzerinde çalıştırılmasına kıyasla gösterilmektedir. Eski JS FS'de her dosya sistemi işleminin ana iş parçacığına proxy'si olması gerekir. Bu da pthread'de bir kat daha yavaş olmasını sağlar. Bunun nedeni, JS FS'nin yalnızca bazı baytları okumak/yazmak yerine kilitleri, sırayı ve beklemeyi içeren iş parçacığı arası iletişim yapmasıdır. Buna karşılık WasmFS, dosyalara herhangi bir iş parçacığında eşit şekilde erişebilir. Bu nedenle grafikte, ana iş parçacığı ile pthread arasında neredeyse hiçbir fark olmadığı gösterilmektedir. Sonuç olarak WasmFS, pthread'te JS FS'den 32 kat daha hızlıdır.
WasmFS'nin 2 kat daha hızlı olduğu ana iş parçacığında da bir fark olduğunu unutmayın. Bunun nedeni, JS FS'nin her dosya sistemi işlemi için JavaScript'i çağırmasıdır. WasmFS ise bunu yapmaz. WasmFS yalnızca gerektiğinde (ör. bir Web API'si kullanmak için) JavaScript kullanır. Bu nedenle, çoğu WasmFS dosyası Wasm'de kalır. Ayrıca, JavaScript gerekli olsa bile WasmFS, kullanıcı tarafından görülebilen gecikmeleri önlemek için ana ileti dizisi yerine yardımcı bir ileti dizisi kullanabilir. Bu nedenle, uygulamanız çoklu iş parçacıklı olmasa bile (veya çoklu iş parçacıklı olsa bile yalnızca ana iş parçacığında dosya kullanıyorsa) WasmFS'yi kullanarak hız iyileştirmeleri görebilirsiniz.
WasmFS'yi aşağıdaki gibi kullanın:
emcc -sWASMFS
WasmFS, üretimde kullanılır ve kararlı kabul edilir ancak henüz eski JS FS'nin tüm özelliklerini desteklemez. Öte yandan, orijinal özel dosya sistemi (kalıcı depolama için önemle tavsiye edilen OPFS) desteği gibi bazı önemli yeni özellikler de içerir. Henüz aktarılmamış bir özelliğe ihtiyacınız yoksa Emscripten ekibi WasmFS'yi kullanmanızı önerir.
Sonuç
Çok sayıda ayırma işlemi yapan veya dosya kullanan çok iş parçacıklı bir uygulamanız varsa WasmFS ve/veya mimalloc
'ten yararlanarak büyük fayda sağlayabilirsiniz. Her ikisini de, bu yayında açıklanan işaretlerle yeniden derleyerek Emscripten projesinde denemek kolaydır.
Hatta iş parçacıkları kullanmıyorsanız bu özellikleri deneyebilirsiniz: Daha modern uygulamalar, daha önce de belirtildiği gibi, bazı durumlarda tek bir çekirdekte bile fark edilen optimizasyonlarla birlikte gelir.