WebAssembly dalam TypeScript

WebAssembly dalam TypeScript

Artikel ini menerangkan tentang WebAssembly dalam TypeScript.

Kami akan menerangkan kaedah praktikal dan mudah difahami untuk mengintegrasikan TypeScript dan WebAssembly.

YouTube Video

WebAssembly dalam TypeScript

WebAssembly (Wasm) ialah runtime format binari yang berjalan hampir pada kelajuan asli dalam pelayar. Dengan memanggil Wasm dari TypeScript, anda boleh menggunakan proses yang memerlukan pengiraan tinggi dan pustaka asli sedia ada yang ditulis dalam C/C++ atau Rust secara berkesan.

Aliran Pelaksanaan Asas

Di sini, kami akan menerangkan aliran pelaksanaan asas untuk Wasm. TypeScript (atau pelayar) mengambil fail .wasm, mewujudkannya (instantiate), dan memanggil fungsi yang dieksport.

    1. Cipta binari .wasm menggunakan AssemblyScript, Rust, C++, atau sediakan yang sedia ada.
    1. Ambil fail .wasm dalam TypeScript (atau dalam pelayar) dan instantiate secara segerak atau tidak segerak.
    1. Panggil fungsi yang dieksport dan kongsi memori menggunakan WebAssembly.Memory jika perlu.

WebAssembly.instantiateStreaming

Seterusnya, kami akan menunjukkan contoh asas pemuatan fail Wasm dan memanggil fungsi yang dieksport. Pelayar perlu menyokong instantiateStreaming.

Kod berikut ialah contoh mendapatkan simple.wasm dari pelayan dan memanggil fungsi add.

 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);
  • Fungsi di dalam Wasm disimpan dalam instance.exports.
  • Oleh kerana TypeScript tidak menerima maklumat jenis, anda perlu menggunakan @ts-ignore atau mencipta definisi jenis anda sendiri.

Aliran Kerja Menggunakan AssemblyScript

AssemblyScript membolehkan anda menulis Wasm dalam sintaks yang serupa dengan TypeScript, menjadikannya pilihan yang mudah diakses untuk pembangun TypeScript. Di sini, kami menyediakan satu fungsi ringkas, membinanya ke dalam .wasm dan .d.ts, serta memanggilnya dari TypeScript.

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}
  • Dengan menggunakan asc (penyusun AssemblyScript), anda boleh menjana fail .wasm dan, jika perlu, fail takrifan jenis .d.ts. Untuk mencubanya secara tempatan, pasang assemblyscript dengan npm dan bina.
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

Berikut ialah contoh pengambilan dan pemanggilan dari sisi TypeScript.

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 memerlukan pengendalian model memori dan rentetan yang teliti, tetapi amat mudah digunakan untuk pengiraan nombor asas.

Rust + wasm-bindgen (Pilihan berkuasa dan biasa digunakan)

Bahagian ini menerangkan aliran kerja menulis Wasm dalam Rust dan menghubungkannya dengan JavaScript atau TypeScript menggunakan wasm-bindgen. Di sini, kami menggunakan fungsi Fibonacci ringkas sebagai contoh untuk menunjukkan cara mengimport modul yang dijana sebagai modul ES.

Eksport fungsi dari sisi Rust menggunakan wasm-bindgen.

 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}
  • Apabila anda bina dengan wasm-pack atau CLI wasm-bindgen, definisi jenis untuk TypeScript dan pembungkus JS dijana, membolehkan anda terus mengimportnya sebagai ESM.
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

Di pihak TypeScript, import dan gunakan modul ES dari pkg.

 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 menjana pembungkus JavaScript dan definisi jenis .d.ts, memudahkan penggunaan dari TypeScript. Sila ambil perhatian bahawa apabila anda menetapkan web untuk pilihan --target dalam perintah wasm-pack, inisialisasi secara asinkron diperlukan.

Contoh Pengkongsian Memori Dunia Sebenar: Pemindahan dan Pemprosesan Array (Peringkat Rendah)

Apabila bertukar data dalam jumlah besar dengan Wasm, pengkongsian ArrayBuffer untuk pertukaran data yang cekap adalah penting. Di sini kami tunjukkan contoh menggunakan AssemblyScript, tetapi prinsip yang sama juga terpakai untuk wasm-bindgen Rust.

Di pihak AssemblyScript, sediakan fungsi eksport untuk menulis ke memori. Sebagai contoh, fungsi untuk mendarab dua setiap elemen dalam array akan kelihatan seperti berikut.

 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}

Untuk menentukan tetapan memori yang digunakan oleh AssemblyScript, sediakan asconfig.json berikut.

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
  • Untuk memanggil fungsi ini, anda perlu menyalin ArrayBuffer ke ruang memori Wasm dan menghantar penunjuk.

Di bawah ialah contoh penggunaan WebAssembly.Memory dalam TypeScript untuk menyalin data dan memanggil fungsi.

 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 ialah memori linear yang dikongsi; meminimumkan salinan dapat meningkatkan kelajuan pemprosesan sebanyak mungkin. Ambil perhatian juga bahawa penunjuk merujuk kepada kedudukan dalam bait, manakala TypedArray diurus berdasarkan bilangan elemen, jadi berhati-hati agar tidak mengelirukan perbezaan ini.

Pengendalian Selamat Jenis: Sediakan Definisi Jenis TypeScript

Eksport Wasm ialah objek JavaScript, jadi menyediakan definisi jenis pada bahagian TypeScript akan memudahkan pembangunan. Berikut ialah contoh ringkas fail definisi jenis.

Berikut menunjukkan definisi jenis minimum yang boleh anda cipta secara manual sebagai simple.d.ts.

1// simple.d.ts
2export function add(a: number, b: number): number;
3export const memory: WebAssembly.Memory;
  • Meletakkan ini dalam typeRoots fail tsconfig.json anda atau menggunakan declare module akan membolehkan penyemakan jenis. wasm-pack dengan mudah menjana fail .d.ts secara automatik, jadi ia sangat berguna untuk digunakan.

Pola Inisialisasi Semasa Runtime: Segerak vs Tidak Segerak

Oleh kerana modul Wasm memerlukan I/O (pengambilan) dan pengkompilan, inisialisasi tidak segerak adalah lazim. Namun, terdapat juga pola di mana anda menyimpan WebAssembly.Module lebih awal dan instantiate secara segerak.

Di bawah ialah struktur kod asas untuk inisialisasi WebAssembly secara tidak segerak. Dalam projek sebenar, pola ini digalakkan.

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}
  • Inisialisasi tidak segerak membolehkan pengendalian ralat dan lazy loading dimasukkan dengan lebih fleksibel, menjadikannya paling mudah dalam pembangunan sebenar. Tambahan pula, kod yang dijana oleh wasm-pack termasuk API init() untuk inisialisasi, jadi membiasakan diri dengan aliran ini akan memudahkan kerja anda.

Pertimbangan Prestasi Praktikal

Berikut adalah beberapa perkara yang perlu diambil perhatian untuk penambahbaikan prestasi ketara. Sila rujuk tip pengoptimuman ini apabila menggabungkan TypeScript dan WebAssembly.

  • Apabila panggilan fungsi berlaku dengan sangat kerap, beban antara pemanggilan JavaScript dan Wasm boleh menjadi halangan (bottleneck). Kami mengesyorkan pengelompokan data dan memprosesnya sekaligus sebanyak mungkin.
  • Peruntukan dan penyalinan memori meningkatkan beban pemprosesan. Gunakan penimbal (buffer) dikongsi dan penunjuk untuk meminimumkan operasi-operasi ini.
  • Berhati-hati semasa mengendalikan nombor apungan (floating-point). Dalam TypeScript, ia menjadi jenis number, tetapi anda boleh mengendalikannya secara tepat dengan memadankan jenis di bahagian Wasm.

Ringkasan

Dengan menggabungkan TypeScript dan WebAssembly, anda boleh mencapai prestasi hampir asli di dalam pelayar. Ini amat berkesan untuk tugasan yang memerlukan pengiraan tinggi atau apabila anda ingin menggunakan aset asli sedia ada. Gabungan ini adalah pilihan yang sangat berkuasa apabila anda ingin meningkatkan prestasi aplikasi web anda.

Anda boleh mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Sila lihat juga saluran YouTube kami.

YouTube Video