Das Wissensportal für IT-Professionals. Entdecke die Tiefe und Breite unseres IT-Contents in exklusiven Themenchannels und Magazinmarken.

SIGS DATACOM GmbH

Lindlaustraße 2c, 53842 Troisdorf

Tel: +49 (0)2241/2341-100

kundenservice@sigs-datacom.de
Ein Web ohne JavaScript ist heute undenkbar. Doch die Skriptsprache bekommt Konkurrenz: von WebAssembly. Dabei handelt es sich um ein maschinennahes Bytecode-Format für den Browser, das unter Berücksichtigung der Hardwareressourcen zu einer schnelleren Ladezeit und Ausführung führt. Aktuell können C oder C++ in WebAssembly-Bytecode übersetzt werden und weitere problemorientierte Sprachen wie C# oder Java sind bereits angedacht. Werden wir also in Zukunft auf JavaScript verzichten können und nur noch Assembler im Web machen oder was hat es hiermit auf sich?
Author Image
Nico Orschel

Author

Author Image
Florian Bader

Author


  • 20.12.2019
  • Lesezeit: 27 Minuten
  • 118 Views

Die Entwicklung von Webanwendungen ist relativ fest in den Händen von HTML (Struktur), CSS (Layout) und JavaScript (Logik). Waren zu Beginn Webseiten statische Gebilde, haben sie sich nicht zuletzt durch die Erfindung von JavaScript (JS) und Integration der Skriptsprache in die Browser zu vollwertigen Applikationen weiterentwickelt. Der Browser als Ausführungsplattform hat sich entsprechend vom Dokumentenviewer zu einem eigenen Ökosystem weiterentwickelt. Aktuell kann man bezüglich Browser eher von den neuen „Betriebssystemen“ sprechen. Browser sowie die zuvor genannten Bestandteile ermöglichen die Ausführung von komplexen Applikationen auf diversen Gerätetypen vom Mobiltelefon über das Smart-TV bis zum Tablet und PC. Alle Formfaktoren haben hier eine gemeinsame Plattform: den Browser. Dieser Umstand hat auch dazu beigetragen, dass mittlerweile viele klassische Anwendungen gleich als Cross-Plattform-Web-App entwickelt werden. Nutzer sollen die gleiche Applikation auf unterschiedlichsten Endgeräten von überall nutzen können. Die Anwendung muss jedoch für die jeweiligen Besonderheiten wie Bildschirmgröße zugeschnitten sein. Durch die wachsende Verbreitung und den wachsenden Umfang von aktuellen JavaScript-basierenden Apps waren im Verlauf der vergangenen Jahre auch die Browserhersteller gezwungen, ihre Engines zur Verarbeitung und Ausführung von JavaScript zu verbessern, sodass aktuelle Web-Apps performant und flüssig ausgeführt werden können. Innovationen aus diesem Bereich haben schlussendlich auch andere JS-basierende App-Plattformen wie Node.JS und Elektron hervorbracht. War JavaScript vorrangig noch für die Programmierung von browserbasierenden Webseiten gedacht, werden plötzlich auch Microservice-orientierte Backend-Logik sowie Anwendungen im klassischen Layout des jeweiligen Betriebssystems in JavaScript geschrieben.
Bei dem rasanten Zuwachs an Popularität im Frontend- und Backend-Bereich ist die Frage spannend, warum plötzlich eine neue Technologie in Form von WebAssembly (WA) im Browser integriert werden soll. Der Artikel klärt zunächst, was WebAssembly ist und welche Problemstellungen damit gelöst werden sollen. Anschließend wird erklärt, was WebAssembly heute schon kann, wo es heute bereits (produktiv) eingesetzt wird und was in Zukunft noch ansteht.

Was ist WebAssembly?

Wer WebAssembly liest, denk erst einmal an klassische Assembler-Sprachen. Müssen wir also zukünftig im Web auch wieder Assembler schreiben? Die Antwort ist nein, denn bei WebAssembly handelt es sich um Bytecode, der im Browser ausgeführt werden kann (Stack-based Virtual Machine). Dabei muss der Entwickler diesen Bytecode nicht selbst schreiben, sondern der Code einer Hochsprache wird, wie bei anderen Programmiersprachen üblich, vom Compiler in den Bytecode übersetzt. Wieso aber wird Bytecode im Browser benötigt? Betrachtet man, wie ein Browser JavaScript in Maschinencode umwandelt (siehe Abbildung 1), so stellt man schnell fest, dass hierfür viele Schritte notwendig sind: JavaScript wird im Textformat ausgeliefert. Dieses muss vom Browser zuerst einmal durch einen Parser in einen Abstract Syntax Tree (AST) überführt werden, um den Text als Objektgraphen abzubilden.
Mit diesem AST kann der Browser dann einen unabhängigen Bytecode erzeugen. Dieser Zwischenschritt ist wichtig, da der Browser später auf unterschiedlichen Prozessorarchitekturen läuft und somit aus dem unabhängigen Bytecode einen spezifischen Maschinencode erstellen kann. Ein weiterer Vorteil von Bytecode ist, dass Optimierungen auf einer höheren Flugebene durchgeführt werden können. Diese Optimierungen im Code zu finden, kostet jedoch Zeit, sie werden deshalb vom Browser nur für sogenannte Hot Paths während der Laufzeit durchgeführt [Meu17]. Hot Paths sind Stellen im Code, die häufig durchlaufen werden und bei denen sich eine Optimierung lohnt. Denn die Zeit, die ein Browser für die Optimierung benötigt, sollte im besten Fall weniger sein, als durch die Optimierung an Ausführungszeit gewonnen wird. Dieser komplette Prozess, während der Laufzeit aus einer Programmiersprache Maschinencode für eine bestimmte Prozessorarchitektur zu erstellen, wird auch Just-intime Compilation (JIT) genannt [Cla17]. Genau bei diesem Problem setzt WebAssembly an. Denn WebAssembly liefert bereits optimierten Bytecode an den Browser. Dadurch ist WebAssembly schnell und effizient, denn der Bytecode wird direkt decodiert und muss im Gegensatz zu JavaScript nicht geparst werden. Auch müssen während der Laufzeit keine größeren Optimierungen durchgeführt werden, da dies bereits vor der Auslieferung an den Browser durch den Compiler erledigt wurde [Liftoff]. Diesen Ansatz nennt man Ahead-of-time Compilation (AOT). Dass WebAssembly bereits auf Typebene optimieren kann, ist ein weiterer Vorteil, der die Performanz erhöht. Denn WebAssembly kennt nur die Datentypen Integer32, Integer64, Float32 und Float64 [WATypes]. Durch die Einschränkung auf diese Datentypen kann der Compiler ebenfalls auf Bytecode-Ebene schon entsprechende Optimierungen der Instruktionen durchführen, da der Datentyp von Variablen und Operationen bereits bekannt ist. Um sicherzustellen, dass WebAssembly später auch gelesen und im Browser über einen Debug-Modus analysiert und getestet werden kann, gibt es sowohl ein Binärformat (Endung: WASM) als auch eine textuelle Repräsentation dieses Binärformats (Endung: WAT) (Beispiele siehe Abbildung 2). Dieses Textformat wird dann im Browser verwendet, um den Code über die gewohnten Browsertools analysieren zu können. Da niemand gerne Assembler, auch wenn es in diesem Fall nur Bytecode ist, analysieren möchte, kann in Zukunft über Source Maps auch der ursprüngliche Quellcode direkt im Browser betrachtet werden [WATooling]. Im Hintergrund kommen bei WebAssembly die gleichen Sicherheitsmechanismen wie bei JavaScript zum Tragen, weshalb zum Beispiel auch WebAssembly-Code in einer eigenen Sandbox wie in JavaScript ausgeführt wird [WASecurity]. Die Idee von WebAssembly ist dabei nicht neu. Bereits 1995 war es mit Java möglich, über Applets Code im Browser auszuführen. Diese Funktionalität war jedoch geknüpft an das Installieren eines Plug-ins im Browser. Ähnliche Ansätze hatten dann auch Flash und Silverlight, die jedoch alle davon abhängig waren, dass der Benutzer das entsprechende Plug-in im Browser installiert hat. Mit (P)NaCl (Portable Native Client) hat Google dann 2011 ein Konzept im Browser Chrome implementiert, welches es diesem ermöglich hat, Nativen Code auszuführen [NaClPNaC]. Im Gegensatz zu ActiveX wurde der Code dabei nicht direkt auf dem System ausgeführt, sondern isoliert in einer Sandbox. Für Applikationen in C und C++ hat Mozilla mit asm.js 2013 dann eine vorerst browserunabhängige Variante geschaffen, mit der systemnaher Code in ein Subset von JavaScript compiliert werden konnte, um diesen im Browser auszuführen [asmjs]. Durch die Einschränkung auf ein Subset konnten die Browser entsprechende Optimierungen anbieten, welche den Code deutlich schneller als übliches JavaScript machen.

Was genau macht WebAssembly nun anders? WebAssembly basiert weder auf einem Browser-Plug-in noch ist es eine browserspezifische Implementierung. Die Spezifikation von WebAssembly ist über das W3C als offener Standard definiert [WASpec]. Die Implementierung dieses Standards ist dann abhängig von den Browserherstellern. Dadurch hat man nicht einzelne Anbieter, die den Standard vorgeben, sondern ein Konsortium aus allen großen Browserherstellern, die sich um diesen kümmern. Auch wird WebAssembly als offener Standard entwickelt, sodass nicht nur die Browserhersteller die Richtung vorgeben, sondern auch jeder Interessierte seine Ideen mit einbringen kann. Mit dieser Rückendeckung ergibt sich eine gewisse Sicherheit rund um WebAssembly, denn so verschwindet der Standard von heute auf morgen nicht so einfach wieder.

Abb. 1: Wie erzeugt ein Browser Maschinencode? [Quelle: Selbstdarstellung]

Abb. 2: Beispiele für WebAssembly-Code am Beispiel von C, WAT und WASM (Quelle: [Wiki])

Warum sollte man sich trotz JavaScript mit WebAssembly beschäftigen?

Ein Problem bei der Entwicklung von JavaScript-basierenden Apps ist, neben Fehlerfreiheit der Software durch entsprechende Sprach- und Toolsupport (siehe z. B. TypeScript), eine konsistente Performanz der Applikation in diversen Browsern, denn Nutzer verwenden eine große Auswahl an verschiedenen Browserversionen über diverse Gerätetypen.


Performanzverhalten

Eine Untersuchung zum Thema Java-Script-Performanz hat Nick Fitzgerald vom Mozilla-Team [Fit18] durchgeführt.
Im Rahmen des Benchmarks wurden verschiedene Algorithmen jeweils einmal als JavaScript- und WebAssembly-Variante in diversen Browsern ausgeführt und das Laufzeitverhalten analysiert. Ein Ergebnis aus den Benchmarks ist in Abbildung 3 dargestellt. Die Ergebnisse der anderen Tests sind relativ ähnlich. Keine wirkliche Überraschung der Analyse war, dass das Laufzeitverhalten der Browser untereinander wegen diversen Engines und Optimierungen unterschiedlich ist und bestimmte Browser häufig mit Topwerten abschneiden. Interessant an den Benchmarks ist, dass die Laufzeit des gleichen JS-Codes in einem Browsertyp eine hohe Streuung hat (siehe roter Bereich in der Abbildung). Wird im gleichen Browser dagegen die WebAssembly-Variante mehrfach ausgeführt, so ist die Spreizung bei Laufzeit nur minimal (siehe blauer Bereich). Der Code verhält sich immer sehr ähnlich bezüglich seiner Performanz-Charakteristika. Einen ähnlichen Performanztest (siehe Abbildung 4) hat Google durchgeführt. Aus Wettbewerbsgründen sind hier aber leider die Browsernamen anonymisiert. Auch dieser Test zeigt, dass die größte Spreizung bezügliche des Laufzeitverhaltens bei JavaScript aufritt. Alle WebAssembly-Varianten hatten selbst in verschiedenen Browsern eine recht ähnliche Performanz. Warum ist dies so? Es gilt zunächst festzuhalten, dass in aktuellen Browsern die maximale Performanz (Peak Performance) zwischen JS und WebAssembly aktuell noch gleich ist. Die performante Ausführung von JavaScript benötigt die Optimierung von Code durch die jeweiligen Browser-Engines. Diese Optimierungen basieren auf Analysen und Prognosen, und wie im richtigen Leben kann auch der Browser mit seiner Prognose falsch liegen. Um eine gesicherte und korrekte Ausführung des Codes sicherzustellen, muss in diesem Fall der Browser auf Fallback-Mechanismen zurückgreifen und ohne Optimierungen auskommen. Das Verfahren heißt hier De-Optimierung (De-Opt) (siehe [Meu17], [Wan19]). Eine De-Optimierung bedeutet schlussendlich eine weniger performante Ausführung des Codes. Das Problem ist, dass die De-Optimierung selbst unter identischen Bedingungen sehr dynamisch, das heißt nicht wirklich gut vorhersehbar ist. Ursache des Problems ist zum Teil auch in der Sprache JavaScript selbst begründet. Exemplarisch sei hier der hohe Freiheitsgrad aufgrund der Typenfreiheit bei dynamischen nicht-typisierten Sprachen genannt. WebAssembly hingegen ermöglicht modernen Compilern sowie dem Browser durch den abgegrenzten Funktionsumfang (im Verhältnis zur Sprache Java-Script) des Bytecodes genauere Analysen und Prognosen (Ahead-of-Time Compiling/Optimierung) bezüglich des Anwendungsverhaltens. Aufgrund dieser Situation müssen bei WebAssembly-Code hingegen die Browser-Engines nie eine De-Optimierung machen.

Erweiterung des Web-Ökosystems

Neben dem Bereich konsistentes Performanzverhalten ist die Erweiterung des Web-Ökosystems ein wichtiges Thema. Das aktuelle JavaScript-Ökosystem ist mit seinen vielen Bibliotheken riesig. Hierzu muss man als Entwickler einfach nur einen Blick in die Package-Registry NPM werfen. Die Auswahl und Verfügbarkeit an unterschiedlichsten Bibliotheken ist enorm. Eine riesige Auswahl bedeutet aber nicht, dass JavaScript deshalb gleich das richtige Werkzeug für jede Problemstellung ist. Jedes Werkzeug hat seine individuellen Stärken und Schwächen. Dies gilt nicht nur für JavaScript, sondern auch für Hochsprachen wie C#, C++ und Java. Technisch lässt sich sicherlich alles irgendwie mit JavaScript abbilden, aber in Bereichen, wo es beispielsweise auf mathematische Genauigkeit (z. B. Rundungsgenauigkeit bei Gleitkommazahlen) oder Beweisbarkeit der Korrektheit von Codes beziehungsweise Algorithmen ankommt, haben als eine Alternative zum Beispiel funktionale Sprachen in einigen Anwendungsbereichen Vorteile, da durch Formal Verification für die Sprache das korrekte Verhalten wissenschaftlich bewiesen wurde. Durch die formale Prüfung hat Programmcode hier tendenziell weniger Fehlerpotenzial. Da WebAssembly als Compiler-Target agiert, können andere Sprachen direkt in WebAssembly übersetzen, aber dennoch existierende Werkzeuge wie Syntaxcheck, statische Codeanalyse usw. weiterverwenden. Hierdurch kann bereits auf Codeebene eine zusätzliche Qualitätssicherung stattfinden. Neben den technischen Aspekten können jetzt auch existierende Algorithmen aus anderen Ökosystemen (z. B. Bild-/Video-/Music-Kompressions-Algorithmen) direkt in eine performante Web-Version, ohne Umweg über eine möglicherweise fehleranfälligere JavaScript-Reimplementierung gehen zu müssen, überführt werden.

Abb. 3: Performanzvergleich zwischen WebAssembly und JavaScript (Analyse Mozilla) (Quelle: [Fit18])

Abb. 4: Performanzvergleich zwischen WebAssembly und JavaScript (Analyse Google) (Quelle [Sur19])

Aktueller Stand von WebAssembly

WebAssembly wird bereits heute von den großen Browsern wie Chrome, Firefox, Edge und Safari unterstützt. Diese Browser beinhalten die Implementierung des Minimum Viable Product (MVP) der WebAssembly-Spezifikation. Das MVP umfasst dabei die Definition von Web-Assembly in Binär- und Textformat. Das Binärformat wird für die Übertragung von WebAssembly genutzt, um bereits hier Übertragungsgröße einzusparen. Das Textformat wird für Debuggingzwecke genutzt, um Entwicklern im Browser das Analysieren des Codes zu ermöglichen.
Zusätzlich gibt es ein festes Set an Instruktionen, die in WebAssembly verwendet werden können. Diese reichen von simpler Addition bis zum Laden und Speichern von Variablen. WebAssembly verfolgt einen eher prozeduralen Ansatz, denn der Code besteht aus einzelnen Modulen und jedes Modul besitzt Funktionen. Um diese Funktionen aufzurufen, muss zuerst eine Instanz des Moduls erzeugt werden. Jedes Modul beschreibt dabei, welche Funktionen nach JavaScript exportiert und welche Funktionen aus JavaScript importiert werden. Außerdem kann jedes Modul noch verschiedene Speicherbereiche definieren, um über diese Daten mit der Außenwelt auszutauschen.

WebAssembly aus Hochsprachen

Zwar kann WebAssembly von Hand geschrieben werden, der Normalfall ist jedoch, dass WebAssembly aus einer Hochsprache compiliert wird. WebAssembly kann aus LLVM-Bytecode, welcher beispielsweise von Compilern für C, C++ und Rust generiert wird, durch den Einsatz von Emscripten erzeugt werden (siehe Abbildung 5). Emscripten kommt bereits bei asm.js zum Einsatz und kann jetzt auch WebAssembly als Zielsprache erzeugen [Emscr]. Um das WebAssembly-Modul auszuführen, wird außerdem JavaScript-Gluecode mitgeliefert. Dieser beinhaltet zusätzlich noch vordefinierte Bindings zwischen WebAssembly und dem Browser. Greift zum Beispiel der native Code auf das File-API zu, so ersetzt der JavaScript-Gluecode diesen mit einem Aufruf gegen das Browser-File-API. Für alle Browser, die aktuell noch kein WebAssembly unterstützen – oder auch für ältere Versionen –, gibt es eine Fallback-Variante über asm.js. Der Vorteil von Emscripten ist, dass es sowohl WebAssembly als auch asm.js erzeugen kann. Somit kann im schlimmsten Fall immer auf den erzeugten asm.js-Code zurückgefallen werden. Neben den genannten Hochsprachen gibt es mittlerweile auch Unterstützung von anderen Programmiersprachen wie C#, Java und Python [GitH-a]. Hier kümmern sich die jeweiligen Compiler-Entwickler der Programmiersprache selbst um die Übersetzung nach WebAssembly. Wofür wird überhaupt noch JavaScript benötigt? Der aktuelle Stand von WebAssembly benötigt noch zwingend Java-Script, das verwendet wird, um WebAssembly zu initialisieren. Im Gegensatz zu JavaScript gibt es für WebAssembly keine HTML-Script-Tags zum Laden und Ausführen. Auch die Kommunikation zwischen dem Browser und WebAssembly findet noch über JavaScript statt. So gibt es Browser-APIs wie den Zugriff auf den DOM (Document Object Model) oder HTTP-Aufrufe, die nicht direkt in WebAssembly gemacht werden können. Es gibt unterschiedliche Strategien für den Einsatz von WebAssembly. Zum einen bietet sich WebAssembly als Ergänzung zu JavaScript an. Da WebAssembly effizient und performant ist, können kritische Stellen damit umgesetzt werden. Diese Stellen können zum Beispiel komplexe Berechnungen (Computation) oder auch grafische Darstellung (Graphics) sein. Die Dokumentation von WebAssembly nennt hier verschiedene Anwendungsfälle [GitH-b]. Zum anderen bietet es sich an, bestehenden nativen Code nach WebAssembly zu portieren, um diesen direkt im Browser auszuführen. Das spart eine Neuentwicklung oder eine Migration nach JavaScript. WebAssembly kann aber auch eine Alternative zu JavaScript sein. Entwickler haben nun die Wahl zwischen JavaScript und anderen Hochsprachen, die wiederum in WebAssembly übersetzt und im Browser ausgeführt werden. Das Thema Cross-Compilation führt dann dazu, dass man sich in Zukunft nicht nur die Frage stellen muss, ob es nun eine Single-Page Application (SPA) wird oder nicht, sondern auch, welche Programmiersprache für das Frontend verwendet werden soll.

Abb. 5: Von nativem Code nach WebAssembly

Was funktioniert heute schon?

Vergleicht man die Anwendungsfälle mit den Szenarien, bei denen heute schon WebAssembly Verwendung findet, so sieht man eine gewisse Überschneidung.

ZBar – Strichcodeerkennung
Ebay hat einen klassischen Anwendungsfall der Bilderkennung mit WebAssembly umgesetzt [Jha19]. Dabei geht es um die effiziente und performante Erkennung von Strichcodes. Der Kunde kann ganz einfach sein Produkt per Strichcode mit einer eingebauten Kamera scannen und alle Produktdetails werden im Hintergrund nachgeladen. Doch die Auswertung von Bilddaten in Echtzeit und das Erkennen des Strichcodes in JavaScript ist nicht immer erfolgreich und auch erst recht nicht performant. Deshalb wurde ZBar [GitH-c], eine Strichcodescanner-Library, die in C implementiert ist, nach WebAssembly compiliert. Diese war zwar performant, hatte aber auch nicht immer eine hundertprozentige Erfolgsquote. Aus diesem Grund wurde die Erkennung parallelisiert, indem zeitgleich die zwei verschiedenen nativen Bibliotheken in WebAssembly und auch eine in JavaScript implementierte Bibliothek die Erkennung versucht haben (siehe Abbildung 6). Wer als Erster ein erfolgreiches Ergebnis zurückliefert, hat gewonnen. Beim ersten Benchmark kamen 87 Prozent der erfolgreichen Ergebnisse von WebAssembly-Implementierungen [Jha19]. Das zeigt, dass die Erkennung nicht nur schneller funktioniert, sondern auch zuverlässig ist.

Abb. 6: Parallelisierte Erkennung eines Strichcodes (Quelle: [Jha19])

PSPDFKit
Mit PSPDFKit gibt es einen weiteren Anwendungsfall, bei dem nicht nur die Performanz von WebAssembly von Vorteil ist, sondern auch die Möglichkeit, bestehenden nativen Code einfach im Web auszuführen. Dabei war die Qual der Wahl, entweder 500.000 Zeilen nativen C++-Code in JavaScript zu portieren oder mithilfe von WebAssembly diesen direkt im Browser ausführbar zu machen [PSP-DFKit-a]. Das Ergebnis war, mit Emscripten nicht nur WebAssembly bereitzustellen, sondern auch eine Fallback-Version mit asm.js. Dadurch ergibt sich ein wunderbarer Benchmark, bei dem eine Implementierung in WebAssembly gegen eine optimierte Implementierung in JavaScript verglichen werden kann. Den Benchmark kann jeder selbst im Browser und Betriebssystem seiner Wahl ausführen [PSP-DFKit-b]. Der Benchmark führt dazu verschiedene Operationen, wie das Rendern der PDF-Seiten im Speicher, aus und misst die Gesamtzeit, die die jeweilige Implementierung dafür benötigt. Da die Browserhersteller stark an der Optimierung von nicht nur WebAssembly, sondern auch von asm.js arbeiten, kann es je nach Browserversion zu starken Abweichungen kommen. Eine aktuelle Ausführung (siehe Abbildung 7) lässt jedoch erkennen, dass je nach Browser die Performanz von WebAssembly zwischen 1,5- und fast 4-mal so schnell wie JavaScript ist. Neben den zuvor genannten zwei Beispielen von auf C++ basierenden existierenden Bibliotheken wird WebAssembly auch schon in größeren Programmierframeworks verwendet. Beispiele aus diesem Bereich sind die Gaming Engine Unity, Node.JS und Microsoft Blazor.

Abb. 7: PSPDF-WebAssembly-Benchmark

Gaming Engine Unity
Unity als Gaming Engine unterstützt als Cross-Plattform-Gaming-Framework unterschiedliche Gerätetypen und Formfaktoren. Der Support geht dabei beispielsweise von Windows PC, TV, Smartphone (Android + iOS) bis zum browserbasierenden Spiel. Für die browserbasierenden Spiele war bis Anfang 2018 das Standard-Linker-Target noch asm.js und WebAssembly ein experimentelles Linker-Target. Ab Unity-Version 2018.1 ist das Experimental-Flag weg und das Standard-Linker-Target jetzt WebAssembly. Unter [Tri18] kann man mehr über die Hintergründe sowie vielfältige, von Browserherstellern unabhängige Benchmarks einsehen. Die Benchmarks bestätigen aber im Wesentlichen die Thesen von Google und Mozilla, dass WebAssembly eine konsistentere Performanz ermöglicht.

Node.JS
Auch an Node.JS ist das Thema WebAssembly nicht vorbeigegangen. Ab Version 8.0 unterstützt es WebAssembly. Spannend ist das Thema hier nicht nur aus Performanz- und Runtime-Aspekten, sondern auch für die Portierbarkeit von nativem Code. Einige Node.JS-Module verwenden für den Zugriff auf plattformspezifische Funktionen oder aus Performanz-Optimierungsgründen Bibliotheken in C++ oder anderen Hochsprachen.

Microsoft Blazor
Einen etwas anderen Primär-Fokus bei der Nutzung von WebAssembly hat Microsoft beim Framework asp.net Core Blazor [Blazor] eingeschlagen. Blazor ermöglicht das Schreiben von kompletten WebApps ohne JavaScript-Code. Hier wird primär alles, was an eigener Client-basierender Logik in JavaScript geschrieben wurde, in einer .NET-Sprache wie beispielsweise C# geschrieben. Der Hauptgrund für diese Designentscheidung ist aber nicht, eine Ablösung für JavaScript als Sprache oder eines Frameworks wie Angular, React usw. zu schaffen, sondern einen alternativen Weg hin zur Webentwicklung für Desktop-Entwickler zu ermöglichen. Blazor selbst kommt aktuell in zwei Varianten, server- und clientseitig. Beim serverseitigen Ansatz wird alle Logik auf dem Server ausgeführt und das Ergebnis (DOM-Änderungen) via WebSocket an den Client zur Darstellung geschickt. WebAssembly hingegen kommt beim clientseitigen Ansatz zum Einsatz. Aktuell ist die clientseitige WebAssembly-Implementierung noch in Preview, da diverse Optimierungen und Features sowohl am Framework als auch WebAssembly im Allgemeinen (siehe Kapitel „Zukunft von WebAssembly“) noch ausstehen. Beim aktuellen clientseitigen Ansatz wird im Browser die bereits existierende .NET-Bibliothek ausgeführt. Die Ausführung wird dabei über eine auf WebAssembly basierende Mono-Runtime ausgeführt. Dieses Laufzeitsystem setzt dabei den .NET IL-Code in WebAssembly-Zwischencode um. Aufgrund dieser Zwischenlösung ist natürlich der Payload, welcher an den Browser gesendet wird, im Verhältnis zu den anderen Technologien noch verhältnismäßig groß und unperformanter. Die Bereiche Performanz und WebAssembly-Payload-Größe werden sehr wahrscheinlich in naher Zukunft noch einige Optimierungen erfahren. Gerade bezüglich des Themas Performanz gibt es hier leider einige Abhängigkeiten von zukünftigen WebAssembly-Funktionalitäten.

Zukunft von WebAssembly

Das MVP ist umgesetzt, doch wo liegt die Zukunft von WebAssembly? Auf der offiziellen Roadmap stehen noch viele Punkte. Einige davon sind notwendig, damit WebAssembly überhaupt zu einer sinnvollen Alternative von JavaScript wird. Denn auch WebAssembly ist nicht perfekt. In seiner aktuellen Implementierung hat WebAssembly einige Probleme: Es gibt nur vier Typen (alle numerisch), es können nur Funktionen importiert und exportiert werden, es gibt keinen Garbage-Collector und WebAssembly-Module können nicht auf Browser-APIs zugreifen. An möglichen Lösungen wird aktuell gearbeitet, die im Folgenden vorgestellt werden sollen.

Typsystem
Mit seinem Typsystem, bestehend aus vier numerischen Typen, ist es nicht einfach, mit komplexere Typen wie Strings, Objekten oder Arrays zu arbeiten. Diese müssen in den von WebAssembly bereitgestellten Speicher serialisiert werden und können dann übermittelt werden. Mit Reference Types ändert sich das. Dadurch ist es jetzt möglich, eine Referenz auf ein JavaScript-Objekt an WebAssembly zu übergeben. Dieses Feature erweitert das Typsystem um den Typ anyref, was eine beliebige Referenz auf ein Objekt aus dem Hostsystem (in diesem Fall JavaScript) sein kann. Diese JavaScript-Objekte werden von der JavaScript-Engine über einen Garbage-Collector verwaltet und müssen deshalb auch in WebAssembly besonders verfolgt werden.

Garbage-Collector
Mit Reference Types ist also ebenfalls schon ein Schritt in Richtung Garbage-Collection vorgedacht. Automatische Speicherbereinigung wird dann interessant, wenn Hochsprachen wie C# oder Java im Browser über WebAssembly ausgeführt werden sollen. Bei diesen verwaltet die jeweilige Runtime selbst ihren Speicher. In WebAssembly gibt es jedoch keine Speicherverwaltung über einen Garbage-Collector. Das führt dazu, dass für die Ausführung solcher Hochsprachen entweder die komplette Runtime oder mindestens ein minimaler Garbage-Collector mitgeliefert und ausgeführt werden muss.

Zugriff auf Browser-APIs
Das größte Problem ist jedoch die Interaktion aus WebAssembly mit Browser-APIs. Da aus WebAssembly-Modulen nicht direkt auf Browser-APIs, wie Aufrufe gegen den DOM oder HTTP-Aufrufe, zugegriffen werden kann, muss aktuell ein Binding in JavaScript angelegt werden (siehe Abbildung 8). Dabei muss zwischen zwei Arten des Zugriffs unterschieden werden. Für Zugriffe auf das JavaScript-API oder auf den DOM müssen entsprechende Funktionen in Hochsprachen bereitgestellt werden, da diese in WebAssembly nicht existieren. Zusätzlich gibt es auch noch Zugriffe, die bereits in den jeweiligen Sprachen vorhanden sind. Hierzu zählen Aufrufe auf das File-API, das vom entsprechenden Browser direkt auf das File-API des Browser gemappt werden kann. Das langfristige Ziel ist, Zugriffe auf das Browser-API direkt aus WebAssembly zu ermöglichen, ohne den Umweg über JavaScript gehen zu müssen. Das verbessert nicht nur die Performanz, sondern reduziert auch unnötigen Gluecode, der bereitgestellt werden muss.

Abb. 8: Zugriff auf Browser-APIs über JavaScript

Multithreading
Mit dem Thema Multithreading gibt es den ersten Ansatz, bei dem WebAssembly gewisse Features besser unterstützt, als JavaScript es heute kann. Ein erster Proof-of-Concept läuft bereits in Chrome [Dan18]. Dort werden Threads aus nativem Code mithilfe von Service Workers und Shared Array Buffers abgebildet. Jeder Thread wird dabei als eigener Service Worker erstellt. Der Austausch zwischen den Threads, dem sogenannten Shared Memory, erfolgt dann über einen Shared Array Buffer. Durch die Sicherheitslücken Spectre und Meltdown war zwischenzeitlich das Konzept Shared Array Buffer deaktiviert, doch Chrome hat mittlerweile über Site Isolation eine Variante gefunden, um diese Funktionalität wieder zu aktivieren. Mit Site Isolation bekommt jede Webseite ihren eigenen Prozess, was das Auslesen des Speichers einer anderen Webseite schwieriger macht. Dadurch kann Multithreading über WebAssembly wieder verwendet werden. Das beste Beispiel für die Anwendung von mehreren Threads ist Google Earth [ChrB19]. Bei Google Earth werden grafische Berechnungen im Hintergrund ausgeführt, die parallelisiert werden können. Je mehr parallele Berechnungen stattfinden, desto mehr Bilder pro Sekunde können gezeichnet werden und desto flüssiger läuft die Applikation im Browser. Ein erster Benchmark (siehe Abbildung 9) zeigt dabei, dass mit Multithreading das Rendering fast doppelt so schnell ist als mit nur einem Thread.

Abb. 9: Performanz von Multithreading (Quelle: [Mea])

Fazit

WebAssembly ist ein Bytecode für den Browser. Mit dem MVP gibt es bereits heute die Möglichkeit, nativen Code in WebAssembly zu übersetzen und diesen performant im Browser auszuführen. Auch die Roadmap für WebAssembly ist gefüllt mit Punkten, die WebAssembly auf lange Sicht noch attraktiver machen. Da die großen Browserhersteller mit Google, Microsoft, Apple und Mozilla hinter WebAssembly stehen, wird es auch kein vorzeitiges Ende dieses Standards geben. Doch was passiert nun mit JavaScript? Müssen Webentwickler in Zukunft auf native Sprachen umschulen, da JavaScript nicht mehr benötigt wird? Die Antwort ist nein, denn auch langfristig wird WebAssembly das JavaScript-Ökosystem nicht ablösen. Vielmehr bietet WebAssembly eine Bereicherung für dieses Ökosystem, indem eine konsistente Performanz für kritische Teile des Codes und neue Funktionalität wie Multithreading geliefert wird. Auch kann WebAssembly als Alternative von JavaScript verwendet werden. So kann bestehender systemnaher Code direkt im Browser wiederverwendet werden, ohne diesen migrieren zu müssen. Dies erspart nicht nur Entwicklungsaufwand, sondern ermöglicht auch eine plattformunabhängige Codebasis, bei der Änderungen nur an einer Stelle gepflegt werden müssen. JavaScript verliert seine Monopolstellung im Web, da es nun auch Alternativen für Logik in Webapplikationen gibt. Entwickler sind nicht mehr darauf angewiesen, JavaScript fürs Frontend zu verwenden, sondern können jetzt auf eine Sprache ihrer Wahl zurückgreifen. WebAssembly ist nicht mehr auf Proofof-Concepts beschränkt, sondern eignet sich auch für reale Anwendungsfälle in Produktivszenario. Deshalb ist WebAssembly hier, um zu bleiben. Sind Sie dafür bereit, dann gilt es nur zu klären, bei was Sie WebAssembly unterstützen kann. ||

Weitere Informationen

[asmjs] asm.js, siehe:
http://asmjs.org/

[Bad19] F. Bader, N. Orschel, Vortrag auf der OOP2019, siehe:
http://sigs.de/download/oop_2019/files/Di24-Bader.pdf

[Blazor] Blazor | Build client web apps with C#, siehe:
https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor

[ChrB19] WebAssembly brings Google Earth to more browsers, Chromium Blog, 20.6.2019, siehe:
https://blog.chromium.org/2019/06/webassembly-brings-google-earth-to-more.html

[Cla17] L. Clark, A crash course in just-in-time (JIT) compilers, 28.2.2017, siehe:
https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/

[Dan18] A. Danilo, WebAssembly Threads ready to try in Chrome 70, Google Developers, siehe:
https://developers.google.com/web/updates/2018/10/wasm-threads

[Emscr] Emscripten - Building to WebAssembly, siehe:
https://emscripten.org/docs/compiling/WebAssembly.html

[Fit18] N. Fitzgerald, Oxidizing Source Maps with Rust and WebAssembly, moz://a Hacks, 18.1.2018, siehe:
https://hacks.mozilla.org/2018/01/oxidizing-source-maps-with-rust-and-webassembly/

[GitH-a] Awesome WebAssembly languages, siehe:
https://github.com/appcypher/awesome-wasm-langs

[GitH-b] WebAssembly Use Cases, siehe:
https://github.com/WebAssembly/design/blob/master/UseCases.md

[GitH-c] ZBar, siehe:
https://github.com/ZBar/ZBar

[Jha19] P. Jha, S. Padmanabhan, WebAssembly at eBay: A Real-World Use Case, 22.5.2019, siehe:
https://tech.ebayinc.com/engineering/webassembly-at-ebay-a-real-world-use-case/

[Liftoff] Liftoff: a new baseline compiler for WebAssembly in V8, 20.8.2018, siehe:
https://v8.dev/blog/liftoff

[Mea] J. Mears, Performance of WebAssembly: a thread on threading, siehe:
https://medium.com/google-earth/performance-of-web-assembly-a-thread-on-threading-54f62fd50cf7

[Meu17] B. Meurer, An Introduction to Speculative Optimization in V8, 28.11.2017, siehe:
https://ponyfoo.com/articles/an-introduction-to-speculative-optimization-in-v8

[NaClPNaC] Native Client (NaCl) and Portable Native Client (PNaCl), siehe:
https://developer.chrome.com/native-client/nacl-and-pnacl

[PSPDFKit-a] G. Gurgone, Ph. Spiess, PSPDFKit: A Real-World WebAssembly Benchmark, siehe:
https://pspdfkit.com/blog/2018/a-real-world-webassembly-benchmark/

[PSPDFKit-b] WebAssembly Benchmark by PSPDFKit, siehe:
https://pspdfkit.com/webassembly-benchmark/

[Sur19] Surma, Replacing a hot path in your app‘s JavaScript with WebAssembly, Google Developers, siehe:
https://developers.google.com/web/updates/2019/02/hotpath-with-wasm

[Tri18] M. Trivellato, WebAssembly is here!, Unity Blog, 15.8.2018, siehe:
https://blogs.unity3d.com/2018/08/15/webassembly-is-here/

[Wan19] A. Wan, How JavaScript works: Optimizing the V8 compiler for efficiency, 13.9.2019, siehe:
https://blog.logrocket.com/how-javascript-works-optimizing-the-v8-compiler-for-efficiency/

[WASecurity] Security, WebAssembly, siehe:
https://webassembly.org/docs/security/

[WASpec] WebAssembly Specifications, siehe:
https://webassembly.github.io/spec/

[WATooling] Tooling support, WebAssembly, siehe:
https://webassembly.org/docs/tooling/

[WATypes] Types, WebAssembly, siehe:
http://webassembly.github.io/spec/core/syntax/types.html#syntax-valtype

[Wiki]
https://en.wikipedia.org/wiki/WebAssembly

. . .

Author Image

Nico Orschel

Author
Zu Inhalten
Nico Orschel ist Principal Consultant, Autor und Referent im Umfeld DevOps und ALM bei der AIT GmbH & Co. KG Stuttgart und wurde von Microsoft als Most Valuable Professional (MVP) für Development Technologies ausgezeichnet. Er hilft Unternehmen, auf Basis von Microsoft Visual Studio Team Foundation Server / Azure DevOps effizienter Software zu entwickeln und zu testen und so ein höheres Qualitätsniveau bei kürzeren Release-Zyklen zu erreichen.
Author Image

Florian Bader

Author
Zu Inhalten
Florian Bader ist Senior Consultant bei der AIT GmbH & Co. KG und wurde von Microsoft als Most Valuable Professional (MVP) für Developer Technologies ausgezeichnet. Er berät Kunden im Bereich Microsoft .NET, DevOps, Cloud und IoT mit Schwerpunkt in Softwareentwicklung und -architektur. Durch seine Erfahrung in der Industrie und sein breitgefächertes Wissen unterstützt er Unternehmen als Know-how-Lieferant in den unterschiedlichsten Bereichen.

Artikel teilen