TypeScript'te WebAssembly

TypeScript'te WebAssembly

Bu makale, TypeScript'te WebAssembly'yi açıklar.

TypeScript ve WebAssembly entegrasyonu için pratik ve kolay anlaşılır yöntemleri açıklayacağız.

YouTube Video

TypeScript'te WebAssembly

WebAssembly (Wasm), tarayıcıda neredeyse yerel hızda çalışan bir ikili biçim çalışma zamanıdır. TypeScript'ten Wasm çağırarak, hesaplama yoğun süreçleri ve C/C++ veya Rust ile yazılmış mevcut yerel kütüphaneleri verimli bir şekilde kullanabilirsiniz.

Temel Çalıştırma Akışı

Burada, Wasm'ın temel çalışma akışını açıklayacağız. TypeScript (veya tarayıcı), .wasm dosyasını alır, başlatır ve dışa aktarılmış fonksiyonları çağırır.

    1. AssemblyScript, Rust, C++ kullanarak bir .wasm ikili dosyası oluşturun veya mevcut olanı hazırlayın.
    1. TypeScript'te (veya tarayıcıda) .wasm dosyasını alın ve bunu eşzamanlı veya eşzamansız olarak başlatın.
    1. Dışa aktarılmış fonksiyonları çağırın ve gerekirse WebAssembly.Memory ile belleği paylaşın.

WebAssembly.instantiateStreaming

Şimdi bir Wasm dosyasının nasıl yükleneceği ve bir dışa aktarılmış fonksiyonun nasıl çağrılacağına dair temel bir örnek göstereceğiz. Tarayıcının instantiateStreaming'i desteklemesi gerekir.

Aşağıdaki kod, simple.wasm dosyasının sunucudan alınması ve add fonksiyonunun çağrılması örneğidir.

 1// TypeScript: load-and-call.ts
 2// Fetch and instantiate a wasm module and call its exported `add` function.
 3async function run() {
 4  const response = await fetch('http://localhost:3000/simple.wasm');
 5  // Use instantiateStreaming when available for efficiency.
 6  const { instance } = await WebAssembly.instantiateStreaming(response, {});
 7  // @ts-ignore
 8  const result = instance.exports.add(2, 3);
 9  console.log('2 + 3 =', result);
10}
11run().catch(console.error);
  • Wasm içindeki fonksiyonlar instance.exports içinde saklanır.
  • TypeScript tip bilgisini almadığı için, @ts-ignore kullanmalı veya kendi tip tanımlarınızı oluşturmalısınız.

AssemblyScript Kullanarak İş Akışı

AssemblyScript, Wasm'ı TypeScript'e benzer bir sözdizimiyle yazmanıza izin verir, bu da TypeScript geliştiricileri için ulaşılabilir bir seçenek kılar. Burada, basit bir fonksiyon hazırlayıp bunu .wasm ve .d.ts dosyalarına derliyoruz ve TypeScript üzerinden çağırıyoruz.

1// assembly/index.ts (AssemblyScript)
2// npm install --save-dev assemblyscript
3
4// Export a simple function to add two integers.
5export function add(a: i32, b: i32): i32 {
6  return a + b;
7}
  • asc (yani AssemblyScript derleyicisi) kullanarak bir .wasm dosyası ve isteğe bağlı olarak bir tip tanım dosyası .d.ts oluşturabilirsiniz. Yerel olarak denemek için assemblyscript'i npm ile kurun ve derleyin.
1# build commands
2# npm install --save-dev assemblyscript
3npx asc assembly/index.ts   -o build/simple.wasm   -t build/simple.wat   --bindings esm   --exportTable   --sourceMap
4
5# optionally generate d.ts with --exportRuntime or use as-bind / loader tools

Burada, TypeScript tarafından nasıl alınır ve çağrılır örneği bulunmaktadır.

1// ts client that loads AssemblyScript-generated wasm
2async function runAssemblyScript() {
3  const res = await fetch('http://localhost:3000/build/simple.wasm');
4  const { instance } = await WebAssembly.instantiateStreaming(res, {});
5  // @ts-ignore
6  console.log('AssemblyScript add:', instance.exports.add(10, 7));
7}
8runAssemblyScript().catch(console.error);
  • AssemblyScript, bellek modelleri ve stringlerin dikkatli yönetimini gerektirir, ancak temel sayısal hesaplamalar için kullanımı oldukça kolaydır.

Rust + wasm-bindgen (Güçlü ve yaygın olarak kullanılan bir seçenek)

Bu bölümde, Wasm'ın Rust dilinde nasıl yazıldığını ve wasm-bindgen kullanılarak JavaScript veya TypeScript ile nasıl köprü kurulduğunu anlatılmaktadır. Burada oluşturulan modülün bir ES modülü olarak nasıl içe aktarılacağını göstermek için basit bir Fibonacci fonksiyonunu örnek olarak kullanıyoruz.

Rust tarafından fonksiyonları wasm-bindgen ile dışa aktarın.

 1// src/lib.rs (Rust)
 2// install wasm-pack from https://drager.github.io/wasm-pack/installer/
 3use wasm_bindgen::prelude::*;
 4
 5// Export a function to JavaScript using wasm-bindgen.
 6#[wasm_bindgen]
 7pub fn fib(n: u32) -> u32 {
 8    if n <= 1 { return n; }
 9    let mut a = 0;
10    let mut b = 1;
11    for _ in 2..=n {
12        let tmp = a + b;
13        a = b;
14        b = tmp;
15    }
16    b
17}
  • wasm-pack veya wasm-bindgen CLI ile derlediğinizde, TypeScript için tip tanımları ve JS sarmalayıcıları otomatik olarak oluşturulur ve bunları doğrudan ESM olarak içe aktarabilirsiniz.
1# build with wasm-pack
2# install wasm-pack from https://drager.github.io/wasm-pack/installer/
3wasm-pack build --target nodejs --out-dir pkg

TypeScript tarafında, pkg klasöründeki ES modülünü içe aktarın ve kullanın.

 1// Node.js: import WASM module built with --target web
 2// import init, { fib } from '../pkg/my_wasm_module.js';
 3// Node.js: import WASM module built with --target nodejs
 4import wasm from '../pkg/my_wasm_module.js';
 5
 6async function run() {
 7  //await init();   // --target web
 8  console.log('fib(10)=', wasm.fib(10));
 9}
10
11run().catch(console.error);
  • wasm-pack, JavaScript sarmalayıcıları ve .d.ts tip tanımları oluşturur; bu da TypeScript'ten kullanımını kolaylaştırır. wasm-pack komutunda --target seçeneği için web belirttiğinizde, asenkron başlatmanın gerektiğini lütfen unutmayın.

Gerçek Dünya Hafıza Paylaşımı Örneği: Dizi Aktarma ve İşleme (Düşük Seviye)

Wasm ile büyük miktarda veri alışverişi yapılırken, verimli veri aktarımı için ArrayBuffer paylaşmak önemlidir. Burada AssemblyScript ile bir örnek gösteriyoruz, ancak aynı ilke Rust'ın wasm-bindgen'i için de geçerlidir.

AssemblyScript tarafında, belleğe yazabilecek dışa aktarılmış bir fonksiyon hazırlayın. Örneğin, bir dizinin her bir elemanını karesini alan bir fonksiyon şu şekilde olabilir.

 1// assembly/array_ops.ts (AssemblyScript)
 2// Square values in place in the wasm linear memory starting at `ptr` for `len` elements.
 3export function square_in_place(ptr: usize, len: i32): void {
 4  // Treat memory as a pointer to 32-bit integers.
 5  for (let i = 0; i < len; i++) {
 6    let offset = ptr + (i << 2); // i * 4 bytes
 7    let value = load<i32>(offset);
 8    store<i32>(offset, value * value);
 9  }
10}

AssemblyScript tarafından kullanılacak bellek ayarlarını belirtmek için aşağıdaki asconfig.json dosyasını hazırlayın.

1{
2  "options": {
3    "memoryBase": 0,
4    "importMemory": false,
5    "initialMemory": 1,
6    "maximumMemory": 10
7  }
8}
1 npx asc assembly/array_ops.ts   -o build/array_ops.wasm   -t build/array_ops.wat   --bindings esm   --exportTable   --sourceMap
  • Bu fonksiyonu çağırmak için, ArrayBuffer'ı Wasm bellek alanına kopyalamanız ve işaretçiyi iletmeniz gerekir.

Aşağıda, verileri kopyalamak ve fonksiyonu çağırmak için TypeScript'te WebAssembly.Memory kullanım örneği bulunmaktadır.

 1// TypeScript: use memory to pass array to wasm
 2async function runArrayOps() {
 3  const res = await fetch('http://localhost:3000/build/array_ops.wasm');
 4  const { instance } = await WebAssembly.instantiateStreaming(res, {});
 5  // @ts-ignore
 6  const memory: WebAssembly.Memory = instance.exports.memory;
 7  // Create a view into wasm memory.
 8  const i32View = new Int32Array(memory.buffer);
 9
10  // Example data
11  const input = new Int32Array([1, 2, 3, 4]);
12  // Choose an offset (in i32 elements) to copy data to (simple example: at index 0).
13  const offset = 0;
14  i32View.set(input, offset);
15
16  // Call wasm function: ptr in bytes, len in elements
17  // @ts-ignore
18  instance.exports.square_in_place(offset * 4, input.length);
19
20  // Read back result
21  const result = i32View.slice(offset, offset + input.length);
22  console.log('squared:', result);
23}
24runArrayOps().catch(console.error);
  • memory.buffer, paylaşılan doğrusal bellek alanıdır; kopyaları minimumda tutmak, işlem hızını olabildiğince artırır. Ayrıca işaretçilerin bayt cinsinden konumları gösterdiğini, ancak TypedArray'lerin eleman sayısına göre yönetildiğini unutmayın; bu nedenle bu farklılıkları karıştırmamaya dikkat edin.

Tip Güvenli Yönetim: TypeScript Tip Tanımlarını Hazırlayın

Wasm dışa aktarımları JavaScript nesneleridir; bu nedenle TypeScript tarafında tip tanımları sağlamak geliştirmeyi kolaylaştıracaktır. İşte bir tip tanımı dosyasının basit bir örneği.

Aşağıda, elle oluşturabileceğiniz simple.d.ts olarak minimal tip tanımı gösterilmektedir.

1// simple.d.ts
2export function add(a: number, b: number): number;
3export const memory: WebAssembly.Memory;
  • Bunu tsconfig.json dosyanızdaki typeRoots içerisine koymak veya declare module kullanmak tip kontrolünü etkinleştirecektir. wasm-pack otomatik olarak .d.ts dosyalarını da üretir; bu nedenle bunları kullanmak faydalı olacaktır.

Çalışma Zamanında Başlatma Şablonları: Senkron ve Asenkron

Wasm modülleri I/O (alma) ve derleme gerektirdiğinden, asenkron başlatma yaygındır. Ancak, WebAssembly.Module'ü önceden önbelleğe alıp, senkron şekilde başlattığınız bir şablon da vardır.

Aşağıda, WebAssembly'yi asenkron başlatmak için temel kod yapısı gösterilmiştir. Gerçek projelerde bu şablon önerilir.

1// async init pattern
2async function initWasm(url: string) {
3  const res = await fetch(url);
4  const { instance, module } = await WebAssembly.instantiateStreaming(res, {});
5  return instance;
6}
  • Asenkron başlatma, hata yönetimini ve tembel yüklemeyi esnek şekilde eklemeyi kolaylaştırır; bu nedenle pratik geliştirmede en kullanışlı olandır. Ayrıca, wasm-pack tarafından üretilen kod, başlatma için bir init() API'si içerir; bu akışa alışmak işlerinizin sorunsuz ilerlemesini sağlar.

Uygulamada Performans İçin Dikkat Edilecekler

Önemli performans iyileştirmeleri için göz önünde bulundurulması gereken bazı noktalar. TypeScript ve WebAssembly'yi birleştirirken bu optimizasyon ipuçlarına başvurun.

  • Fonksiyon çağrıları çok sık olduğunda, JavaScript ile Wasm arasındaki çağrı yükü darboğaz haline gelebilir. Verileri toplu olarak işleyip tek seferde işlemeyi mümkün olduğunca öneririz.
  • Bellek ayırma ve kopyalama, işleme yükünü artırır. Bu işlemleri en aza indirmek için paylaşılan arabellekler ve işaretçiler kullanın.
  • Kayan noktalı sayılarla çalışırken dikkatli olun. TypeScript'te bunlar number türüne dönüşür; ancak Wasm tarafında tipleri eşleştirerek bunları doğru bir şekilde işleyebilirsiniz.

Özet

TypeScript ve WebAssembly'yi birleştirerek tarayıcıda neredeyse yerel performans elde edebilirsiniz. Bu, özellikle hesaplamaya dayalı işlemler veya mevcut yerel varlıkları kullanmak istediğinizde etkilidir. Bu kombinasyon, web uygulamanızın performansını artırmak istediğinizde çok güçlü bir seçenektir.

Yukarıdaki makaleyi, YouTube kanalımızda Visual Studio Code'u kullanarak takip edebilirsiniz. Lütfen YouTube kanalını da kontrol edin.

YouTube Video