Einführung in die fortgeschrittene Arbeit mit WebAssembly und TypeScript
\n\nWebAssembly (WASM) ist längst keine experimentelle Technologie mehr. Heute ist es ein leistungsstarkes Werkzeug für leistungskritische Aufgaben: Bildverarbeitung, Kryptographie, Simulationen, Compiler und Spiel-Engines. Das einfache Laden eines WASM-Moduls im Browser ist jedoch nur die Spitze des Eisbergs. Auf fortgeschrittener Ebene stehen Entwickler vor der Notwendigkeit eines effizienten Speichermanagements, der Übergabe komplexer Datenstrukturen und einer tiefen Integration mit TypeScript.
\n\nIn diesem Artikel behandeln wir die wichtigsten Techniken, die es Ihnen ermöglichen, ein neues Niveau der Beherrschung der Kombination WebAssembly + TypeScript zu erreichen. Sie erfahren, wie Sie die Datenübergabe optimieren, mit linearem Speicher arbeiten, leistungsstarke Abstraktionen erstellen und WASM-Module debuggen.
\n\n1. Verwaltung des linearen Speichers und Übergabe komplexer Strukturen
\n\nEine der Hauptfunktionen von WebAssembly ist die Arbeit mit linearem Speicher. Im Gegensatz zu JavaScript, wo Sie mit Objekten und Garbage Collection arbeiten, manipulieren Sie in WASM direkt Bytes. Für fortgeschrittene Arbeiten müssen Sie in der Lage sein, Speicher effizient zwischen den beiden Umgebungen zuzuweisen, freizugeben und zu übertragen.
\n\n1.1. Speicherzuweisung und Arbeit mit Zeigern
\n\nBeim Kompilieren von C/C++-Code über Emscripten oder bei Verwendung von AssemblyScript erhalten Sie die Funktionen malloc und free. In TypeScript können wir diese direkt für die manuelle Speicherverwaltung aufrufen.
// TypeScript\ninterface WasmExports {\n malloc: (size: number) => number;\n free: (ptr: number) => void;\n processData: (inputPtr: number, inputLen: number) => number;\n}\n\nconst wasmModule: WebAssembly.Instance = await loadWasm();\nconst exports = wasmModule.exports as unknown as WasmExports;\n\n// Erstellen eines Zahlen-Arrays im WASM-Speicher\nconst data = new Float32Array([1.5, 2.7, 3.14, 4.0]);\nconst bytesPerElement = Float32Array.BYTES_PER_ELEMENT;\nconst totalBytes = data.length * bytesPerElement;\n\n// Speicher zuweisen und Daten kopieren\nconst ptr = exports.malloc(totalBytes);\nconst wasmMemory = new Float32Array(\n (wasmModule.exports.memory as WebAssembly.Memory).buffer,\n ptr,\n data.length\n);\nwasmMemory.set(data);\n\n// WASM-Funktion aufrufen\nconst resultPtr = exports.processData(ptr, data.length);\n\n// Ergebnis lesen\nconst result = new Float32Array(\n (wasmModule.exports.memory as WebAssembly.Memory).buffer,\n resultPtr,\n 1\n);\nconsole.log('Ergebnis:', result[0]);\n\n// Speicher freigeben\nexports.free(ptr);\nexports.free(resultPtr);\n\n1.2. Übergabe von Zeichenketten und Objekten
\n\nZeichenketten in WASM sind einfach Byte-Sequenzen. Für ihre Übergabe aus TypeScript muss die Zeichenkette in UTF-8 kodiert und ein Zeiger auf den Datenanfang zusammen mit der Länge übergeben werden.
\n\n// Hilfsfunktion zum Kodieren einer Zeichenkette\nfunction stringToWasm(str: string, exports: WasmExports): { ptr: number; len: number } {\n const encoder = new TextEncoder();\n const encoded = encoder.encode(str);\n const ptr = exports.malloc(encoded.length);\n const wasmBuffer = new Uint8Array(\n (exports.memory as WebAssembly.Memory).buffer,\n ptr,\n encoded.length\n );\n wasmBuffer.set(encoded);\n return { ptr, len: encoded.length };\n}\n\n// Verwendung\nconst { ptr, len } = stringToWasm("Hallo, WASM!", exports);\nconst resultPtr = exports.processString(ptr, len);\nconst result = readCString(resultPtr, exports); // Funktion zum Lesen einer Zeichenkette\nexports.free(ptr);\nexports.free(resultPtr);\n\n2. Leistungsoptimierung: Minimierung von Übergängen
\n\nJeder Aufruf von TypeScript zu WASM und zurück verursacht Overhead. Für leistungsintensive Anwendungen (z. B. eine Physik-Engine oder Echtzeit-Videoverarbeitung) muss die Anzahl solcher Übergänge minimal sein.
\n\n2.1. Stapelverarbeitung von Daten
\n\nAnstatt die WASM-Funktion für jedes Element aufzurufen, mass