WebAssembly'yi bu kuruluma nasıl entegre edersiniz? Bu makalede, örnek olarak C/C++ ve Emscripten'i kullanarak bunu işleyeceğiz.
WebAssembly (Wasm) genellikle bir performans temel öğesi olarak veya mevcut C++ kod tabanından biri haline geldi. squoosh.app ile kullanıcılara en azından üçüncü bir perspektif daha olduğunu belirtiyor: ekosistemleri anlamına gelir. Entegre Emscripten, C/C++ kodunu kullanabilirsiniz. Rust wasm desteği yerleşik olarak bulunur ve Go ekibi de üzerinde çalışıyor. Ben ve başka birçok dil de gelecektir.
Bu senaryolarda wasm, uygulamanızın merkezinde değil, bir bulmacadan ziyade bir parça: Bir başka modül. Uygulamanızda zaten JavaScript, CSS, resim öğeleri, web merkezli bir derleme sistemi ve belki React gibi bir çerçeve de olabilir. Peki, bu kuruluma WebAssembly entegre edilsin mi? Bu makalede, proje yaşam döngüsü C/C++ ve Emscripten örnek olarak verilebilir.
Docker
Emscripten ile çalışırken Docker'ın çok değerli olduğunu düşünüyorum. C/C++ kitaplıklar genellikle temel aldıkları işletim sistemiyle çalışacak şekilde yazılır. Tutarlı bir ortama sahip olmak inanılmaz derecede faydalı. Docker sayesinde kurulumu yapılmış ve Emscripten ile birlikte çalışacak şekilde ayarlanmış yüklü tüm araçlar ve bağımlılıklar. Eksik bir şey varsa kendi makinenizi nasıl etkileyeceği konusunda endişelenmenize gerek kalmadan işleyeceğiz. Bir şeyler ters giderse kabı atın ve bitti. Bir kez çalışırsa çalışmaya devam edeceğinden emin olabilirsiniz. aynı sonuçları vermelidir.
Docker Registry için emscripten resmi tarafından kullandığım trzeci.
npm ile entegrasyon
Çoğu durumda, bir web projesine giriş noktası npm'dir.
package.json
Geleneksel olarak çoğu proje npm install &&
npm run build
ile oluşturulabilir.
Genel olarak, Emscripten tarafından üretilen yapı yapıları (.js
ve .wasm
dosyası) yalnızca başka bir JavaScript modülü olarak ve
öğe. JavaScript dosyası, webpack veya rollup gibi bir paketleyici tarafından işlenebilir.
ve wasm dosyası,
resim.
Bu nedenle, Emscripten derleme yapılarının "normal" ve geliştirme süreci devreye girer:
{
"name": "my-worldchanging-project",
"scripts": {
"build:emscripten": "docker run --rm -v $(pwd):/src trzeci/emscripten
./build.sh",
"build:app": "<the old build command>",
"build": "npm run build:emscripten && npm run build:app",
// ...
},
// ...
}
Yeni build:emscripten
görevi Emscripten'i doğrudan çağırabilir, ancak
Daha önce de belirttiğim gibi, derleme ortamının doğru olduğundan emin olmak için
tutarlı olması gerekir.
docker run ... trzeci/emscripten ./build.sh
, Docker'a yeni bir komut dosyası başlatmasını söyler
container'ı eklemek için trzeci/emscripten
görüntüsünü kullanın ve ./build.sh
komutunu çalıştırın.
build.sh
, bundan sonra yazacağınız bir kabuk komut dosyasıdır. --rm
anlatıyor
Docker'ın, çalışması tamamlandıktan sonra container'ı silmesini. Bu sayede, projenizin
zamanla eski makine görüntüleri koleksiyonunu ortaya çıkarır. -v $(pwd):/src
şu anlama gelir:
Docker'ın "yansıtmasını" istiyorsanız içindeki /src
konumuna geçerli dizin ($(pwd)
)
yerleştirilmelidir. Dizinin içindeki /src
dizininden dosyalarda yaptığınız tüm değişiklikler
yansıtılacağından emin olun. Bu yansıtılan dizinler
"bağlama düzenekleri" olarak adlandırılır.
build.sh
ürününe göz atalım:
#!/bin/bash
set -e
export OPTIMIZE="-Os"
export LDFLAGS="${OPTIMIZE}"
export CFLAGS="${OPTIMIZE}"
export CXXFLAGS="${OPTIMIZE}"
echo "============================================="
echo "Compiling wasm bindings"
echo "============================================="
(
# Compile C/C++ code
emcc \
${OPTIMIZE} \
--bind \
-s STRICT=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s MALLOC=emmalloc \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-o ./my-module.js \
src/my-module.cpp
# Create output folder
mkdir -p dist
# Move artifacts
mv my-module.{js,wasm} dist
)
echo "============================================="
echo "Compiling wasm bindings done"
echo "============================================="
Burada incelenmesi gereken çok şey var.
set -e
, kabuğu "başarısız" durumuna geçirir yatırım yapmanız önemlidir. Komut dosyasında herhangi bir komut varsa
hata döndürürse komut dosyasının tamamı hemen iptal edilir. Bu özellik,
son derece faydalıdır çünkü komut dosyasının son çıktısı her zaman başarılı olur.
mesajını veya derlemenin başarısız olmasına neden olan hatayı içerir.
export
ifadeleriyle birkaç ortamın değerlerini tanımlarsınız
değişkenlerine karşılık gelir. Ek komut satırı parametrelerini C'ye aktarmanıza olanak tanır.
derleyici (CFLAGS
), C++ derleyici (CXXFLAGS
) ve bağlayıcı (LDFLAGS
).
Tüm kullanıcılar, aşağıdakilerin sağlandığından emin olmak için optimize edici ayarlarını OPTIMIZE
üzerinden alır:
her şey aynı şekilde optimize edilir. Birkaç olası değer vardır
OPTIMIZE
değişkeni için:
-O0
: Hiçbir optimizasyon yapmayın. Ölü kod elenmez ve Emscripten, kaynaklandığı JavaScript kodunu da küçültmez. Hata ayıklama için uygundur.-O3
: Performans için agresif şekilde optimizasyon yapar.-Os
: İkincil olarak performans ve boyut için agresif bir şekilde optimizasyon yapın ölçütü olarak kullanabilirsiniz.-Oz
: Boyut için agresif bir şekilde optimizasyon yapar, gerekirse performanstan ödün verir.
Web için çoğunlukla -Os
öneririm.
emcc
komutunun kendine özgü birçok seçeneği vardır. ECC'nin
"GCC veya clang gibi derleyiciler için kullanıma sunulan bir alternatif olması gerekir". Yani tüm
veya GCC'den öğrenebileceğiniz işaretler, muhtemelen emcc tarafından
olur. -s
işareti, Emscripten'i yapılandırmamıza olanak tanıması açısından özeldir.
merak ediyor. Mevcut tüm seçenekleri Emscripten'in
settings.js
ama bu dosya oldukça zorlayıcı olabilir. Emscripten flag'lerinin listesi
önemli olduğunu düşünüyorum:
--bind
şunu etkinleştirir: embind.-s STRICT=1
, kullanımdan kaldırılan tüm derleme seçeneklerini desteklemez. Bu sayede bir şekilde derlendiğinden emin olun.-s ALLOW_MEMORY_GROWTH=1
, şu durumlarda belleğin otomatik olarak büyütülmesine izin verir gerekir. Emscripten, yazının yazıldığı sırada 16 MB bellek tahsis eder. ilk adımıdır. Kodunuz bellek parçalarını ayırdığından bu seçenek, bu işlemler, bellek yetersiz olduğunda wasm modülünün tamamının başarısız olmasına ya da birleştirici kodun toplam belleği 50, 00 ABD doları veya kabul etmesini sağlar.-s MALLOC=...
, hangimalloc()
uygulamasının kullanılacağını seçer.emmalloc
Emscripten için özel olarak küçük ve hızlı birmalloc()
uygulamasıdır. İlgili içeriği oluşturmak için kullanılan alternatifi olandlmalloc
, tam kapsamlı birmalloc()
uygulamasıdır. Yalnızca siz çok sayıda küçük nesne ayırıyorsanızdlmalloc
öğesine geçmeniz gerekir veya ileti dizisi görünümü kullanmak istiyorsanız.-s EXPORT_ES6=1
, JavaScript kodunu ES6 modülüne dönüştürür. tüm paketleyicilerle çalışan varsayılan dışa aktarım olabilir. Ayrıca-s MODULARIZE=1
tarafından şunun için de gereklidir: ayarlayabilirsiniz.
Aşağıdaki işaretler her zaman gerekli değildir veya yalnızca hata ayıklamaya yardımcı olur amaçlar:
-s FILESYSTEM=0
, Emscripten ile ilgili bir işarettir ve C/C++ kodunuz dosya sistemi işlemlerini kullandığında sizin için bir dosya sistemi emülasyonu oluşturabilir. Test sırasında dosya sistemi emülasyonunu birleştirmenize olanak tanımaz. Ancak bazen bu analizde hata olabilir ve 70 kB'lık hacme ek olarak tutkal için dosya sistemi emülasyonunun kodunu ekleyebilirsiniz.-s FILESYSTEM=0
ile Emscripten'i bu kodu dahil etmemeye zorlayabilirsiniz.-g4
, Emscripten'in.wasm
ve wasm modülü için bir kaynak eşleme dosyası da oluşturur. Daha fazla bilgi için Emscripten ile hata ayıklama hata ayıklama bölümünü inceleyin.
İşlem tamam! Bu kurulumu test etmek için çok küçük bir my-module.cpp
oluşturalım:
#include <emscripten/bind.h>
using namespace emscripten;
int say_hello() {
printf("Hello from your wasm module\n");
return 0;
}
EMSCRIPTEN_BINDINGS(my_module) {
function("sayHello", &say_hello);
}
Ayrıca index.html
:
<!doctype html>
<title>Emscripten + npm example</title>
Open the console to see the output from the wasm module.
<script type="module">
import wasmModule from "./my-module.js";
const instance = wasmModule({
onRuntimeInitialized() {
instance.sayHello();
}
});
</script>
(Burada bir özet ekleyin.)
Her şeyi derlemek için
$ npm install
$ npm run build
$ npm run serve
localhost:8080 adresine gittiğinizde Geliştirici Araçları konsolu:
Bağımlılık olarak C/C++ kodu ekleme
Web uygulamanız için bir C/C++ kitaplığı oluşturmak istiyorsanız,
projenizin bir parçasıdır. Kodu projenizin deposuna manuel olarak ekleyebilirsiniz
ya da bu tür bağımlılıkları yönetmek için npm kullanabilirsiniz. Örneğin,
web uygulamamda libvpx'i kullanmak istiyorum. libvpx
.webm
dosyalarında kullanılan codec'i olan VP8 ile resimleri kodlamak için kullanılan bir C++ kitaplığıdır.
Ancak, libvpx npm'de değil ve bir package.json
içermediğinden emin olamıyorum
npm'yi kullanarak doğrudan yükleyin.
Bu ikilemden kurtulmak için
napa. napa, herhangi bir Git'i yüklemenize olanak tanır.
depo URL'sini node_modules
klasörünüze bağımlılık olarak ekleyebilirsiniz.
Bağımlılık olarak napa'yı yükleyin:
$ npm install --save napa
ve napa
öğesini bir yükleme komut dosyası olarak çalıştırdığınızdan emin olun:
{
// ...
"scripts": {
"install": "napa",
// ...
},
"napa": {
"libvpx": "git+https://github.com/webmproject/libvpx"
}
// ...
}
npm install
çalıştırdığınızda napa, libvpx GitHub'ı klonlama işlemini gerçekleştirir
kod deposuna libvpx
adı ile node_modules
ekleyin.
Derleme komut dosyanızı artık libvpx'i derleyecek şekilde genişletebilirsiniz. libvpx configure
kullanır
make
ve oluşturulacak. Neyse ki Emscripten, configure
ve
make
, Emscripten'in derleyicisini kullanır. Bu amaçla sarmalayıcılar
emconfigure
ve emmake
komutlarını içerir:
# ... above is unchanged ...
echo "============================================="
echo "Compiling libvpx"
echo "============================================="
(
rm -rf build-vpx || true
mkdir build-vpx
cd build-vpx
emconfigure ../node_modules/libvpx/configure \
--target=generic-gnu
emmake make
)
echo "============================================="
echo "Compiling libvpx done"
echo "============================================="
echo "============================================="
echo "Compiling wasm bindings"
echo "============================================="
# ... below is unchanged ...
C/C++ kitaplığı iki bölüme ayrılır: başlıklar (geleneksel olarak .h
veya
.hpp
dosyaları) içeren standart bir yapılandırma dosyasıdır.
kitaplığını gösterir ve gerçek kitaplığı (geleneksel olarak .so
veya .a
dosyaları) içerir. Alıcı:
kodunuzda kitaplığın VPX_CODEC_ABI_VERSION
sabitini kullanırsanız
bir #include
ifadesi kullanarak kitaplığın başlık dosyalarını dahil etmek için:
#include "vpxenc.h"
#include <emscripten/bind.h>
int say_hello() {
printf("Hello from your wasm module with libvpx %d\n", VPX_CODEC_ABI_VERSION);
return 0;
}
Sorun, derleyicinin vpxenc.h
öğesini nerede arayacağını bilmemesidir.
-I
işaretinin işlevi budur. Derleyiciye, hangi dizinlerin
başlık dosyalarını kontrol edin. Buna ek olarak, derleyiciye
gerçek kitaplık dosyası:
# ... above is unchanged ...
echo "============================================="
echo "Compiling wasm bindings"
echo "============================================="
(
# Compile C/C++ code
emcc \
${OPTIMIZE} \
--bind \
-s STRICT=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s ASSERTIONS=0 \
-s MALLOC=emmalloc \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-o ./my-module.js \
-I ./node_modules/libvpx \
src/my-module.cpp \
build-vpx/libvpx.a
# ... below is unchanged ...
npm run build
öğesini şimdi çalıştırırsanız bu işlemin yeni bir .js
oluşturduğunu görürsünüz.
ve yeni bir .wasm
dosyası ekleyerek demo sayfasının gerçekten de sabit değer çıktısını verir:
Ayrıca derleme işleminin uzun sürdüğünü de fark edeceksiniz. Çalışmanın
uzun derleme süreleri değişiklik gösterebilir. libvpx'de bu çok uzun sürer çünkü
her çalıştırdığınızda hem VP8 hem de VP9 için bir kodlayıcı ve bir kod çözücü derler.
kaynak dosyalar değişmemiş olsa bile derleme komutunuzu kullanmanız gerekir. Küçük bir hesap bile
my-module.cpp
üzerinde yapacağınız değişikliklerin oluşturulması uzun sürer. Her ne kadar
libvpx derleme yapılarını, yüklendikten sonra
inşa etmek istiyorum.
Bunu sağlamanın bir yolu da ortam değişkenlerini kullanmaktır.
# ... above is unchanged ...
eval $@
echo "============================================="
echo "Compiling libvpx"
echo "============================================="
test -n "$SKIP_LIBVPX" || (
rm -rf build-vpx || true
mkdir build-vpx
cd build-vpx
emconfigure ../node_modules/libvpx/configure \
--target=generic-gnu
emmake make
)
echo "============================================="
echo "Compiling libvpx done"
echo "============================================="
# ... below is unchanged ...
(Kısa bir özet içerir.)
eval
komutu, parametreleri ileterek ortam değişkenlerini ayarlamamıza olanak tanır.
derleme komut dosyasına geçelim. Aşağıdaki durumlarda test
komutu libvpx'i derlemeyi atlar:
$SKIP_LIBVPX
herhangi bir değere ayarlanmış.
Artık modülünüzü derleyebilirsiniz ancak libvpx'i yeniden oluşturmayı atlayabilirsiniz:
$ npm run build:emscripten -- SKIP_LIBVPX=1
Derleme ortamını özelleştirme
Bazen kitaplıklar derleme için ek araçlara ihtiyaç duyar. Bu bağımlılıklar
Docker görüntüsü tarafından sağlanan derleme ortamında yoksa
bunları kendiniz ekleyin. Örneğin, diyelim ki bir hafta içinde
doxygen kullanarak libvpx ile ilgili dokümanlar Doxsijen
Docker container'ınızın içinde bulunur ancak bunu apt
kullanarak yükleyebilirsiniz.
Bu işlemi build.sh
içinde yapmanız durumunda, uygulamanızı yeniden indirip yeniden yüklerdiniz
doxygen'i kullanabilirsiniz. Bu, proje yönetiminin
hem de çevrimdışıyken projenizde çalışmanıza engel olur.
Burada kendi Docker görüntünüzü oluşturmanız önerilir. Docker görüntüleri,
bir Dockerfile
yazmanız gerekir. Dockerfile'lar oldukça
güçlü ve birçok uygulamaya
komutlarına sahip olmakla birlikte,
zamandan tasarruf etmek için FROM
, RUN
ve ADD
kullanmaya başlayabilirsiniz. Bu durumda:
FROM trzeci/emscripten
RUN apt-get update && \
apt-get install -qqy doxygen
FROM
ile, başlangıç olarak kullanmak istediğiniz Docker görüntüsünü belirtebilirsiniz.
puan. Temel olarak trzeci/emscripten
seçeneğini seçtim (kullandığınız resim)
emin olmanız gerekir. RUN
ile Docker'a kabuk komutlarını
emin olun. Bu komutların kapsayıcıda yaptığı değişiklikler artık
Docker görüntüsü vardır. Docker görüntünüzün oluşturulduğundan ve
build.sh
çalıştırmadan önce package.json
ayarınızı yapmanız gerekir.
bit:
{
// ...
"scripts": {
"build:dockerimage": "docker image inspect -f '.' mydockerimage || docker build -t mydockerimage .",
"build:emscripten": "docker run --rm -v $(pwd):/src mydockerimage ./build.sh",
"build": "npm run build:dockerimage && npm run build:emscripten && npm run build:app",
// ...
},
// ...
}
(Kısa bir özet içerir.)
Bu işlem Docker görüntünüzü ancak henüz oluşturulmadıysa oluşturur. Sonra
her şey eskisi gibi çalışıyor, ancak şimdi derleme ortamında doxygen
var
komutu kullanılabilir durumda olduğundan, libvpx belgelerinin
olur.
Sonuç
C/C++ kodu ile npm'nin doğal olarak uygun olmaması şaşırtıcı değildir, ancak bazı ek araçlar ve yalıtım ile oldukça rahat çalışmasını tek bir web sitesidir. Bu kurulum her projede kullanılamaz ancak iyi bir başlangıç noktası ekleyebilirsiniz. Mevcut lütfen paylaşın.
Ek: Docker görüntü katmanlarından yararlanma
Alternatif bir çözüm de bu sorunların daha fazlasını Docker ve Docker'ın önbelleğe alma konusundaki akıllı yaklaşımı. Docker, Dockerfile'ları adım adım yürütür her adımın sonucunu kendine ait bir resim atar. Bu orta düzey görseller genellikle "katmanlar" olarak adlandırılır. Dockerfile'daki bir komut değişmediyse Docker Dockerfile'ı yeniden oluştururken bu adımı yeniden çalıştırmaz. Bunun yerine resmin son oluşturulduğu andaki katmanı yeniden kullanır.
Daha önce, her seferinde libvpx'i yeniden oluşturmamak için biraz çaba göstermeniz gerekiyordu.
yardımcı olursunuz. Bunun yerine, libvpx'teki yapı talimatlarını taşıyabilirsiniz
Docker'ın önbelleğe alma özelliğinden yararlanmak için build.sh
üzerinden Dockerfile
ile aktarma
mekanizma:
FROM trzeci/emscripten
RUN apt-get update && \
apt-get install -qqy doxygen git && \
mkdir -p /opt/libvpx/build && \
git clone https://github.com/webmproject/libvpx /opt/libvpx/src
RUN cd /opt/libvpx/build && \
emconfigure ../src/configure --target=generic-gnu && \
emmake make
(Kısa bir özet içerir.)
docker build
çalıştırılırken bağlama bağlantıları. Herhangi bir yan etki olarak,
artık çok kolay.