Nowy Firefox uruchomi gry i aplikacje webowe tak szybko, jakby ładował obrazki

Strona główna Aktualności
image

O autorze

Po długich latach zmagań między rozwiązaniami mającymi pozwolić na jak najszybsze uruchamianie aplikacji w przeglądarkach, w 2015 roku wyłonił się standard WebAssembly. Definiuje on binarny format plików .wasm i odpowiadający mu źródłowy format tekstowy kodu przypominającego assembler. Kod taki uruchamiany jest na przenośnej maszynie stosowej, z szybkością bliską szybkości natywnego kodu, w bezpiecznym sandboksie, po przejściu formalnej weryfikacji. Co najważniejsze, obecnie wszystkie najważniejsze przeglądarki są już w stanie uruchamiać aplikacje dostarczane w postaci plików .wasm, a pod względem możliwości aplikacje te mogą to samo, co aplikacje w JavaScripcie. Każdy z producentów ma jednak własną implementację standardu, a różnią się one między innymi wydajnością dekodowania binarnych plików i ich kompilacji. Różnice były jednak niewielkie – aż do teraz. Mozilla znalazła sposób, by radykalnie przyspieszyć ten proces.

WebAssembly a JavaScript

Silniki JavaScriptu osiągnęły granice możliwej optymalizacji. Pewnych rzeczy nie da się przeskoczyć. Przeglądarka pobierając kod w JavaScripcie pobiera go w formie tekstowej (nawet jeśli skrypt został zminimalizowany). Potem parser musi przetworzyć go w drzewo składni abstrakcyjnej, czyli rozpisanie logiki kodu na węzły połączone według znaczących konstrukcji i składowych. Parsery robią to zwykle po łebkach, przetwarzając tylko te funkcje, które zostaną zaraz bezpośrednio wywołane, a zamiast reszty wstawiając stuby (czyli puste obiekty). Finalnie takie drzewo składniowe zostaje przekształcone na bajtowy kod pośredniczący, który przechodzi swoje wstępne procesy optymalizacji, a następnie jest uruchomiany i jednocześnie optymalizowany w trakcie uruchomienia na silniku skryptowym tej konkretnej przeglądarki.

W wypadku WebAssembly sytuacja wygląda znacznie prościej. Przeglądarka dostaje zwarty kod pośredniczący w binarnej postaci. Musi on zostać jedynie zdekodowany (jest to nawet 20 razy szybsze od parsowania) i sprawdzony pod kątem błędów, potem zostaje skompilowany i uruchomiony. Sam proces kompilacji zależy od przeglądarki, ale tak czy inaczej jest o wiele wydajniejszy, choćby ze względu na statyczną typizację (jak np. w C/C++) i to, że kod WebAssembly jest efektem pracy kompilatora, a nie człowieka. W efekcie aplikacja WebAssembly nie tylko ładuje się znacząco szybciej, ale też działa później w przeglądarce o wiele wydajniej.

Oczywiście celem WebAssembly nie jest zastąpienie JavaScriptu, raczej jego uzupełnienie – chodzi o danie programistom lepszej platformy do uruchamiania złożonych, wymagających aplikacji, pisanych przede wszystkim w C/C++, a nie sterowanie w tym języku interfejsami użytkownika stron internetowych.

Wyścigi na nowym torze

Wydajność silników JavaScriptu wiodących przeglądarek jest dziś zbliżona. Można powiedzieć że są benchmarki, w których wygra Chrome, są takie, które pokażą wyższość Firefoksa, znają się nawet takie, w których najlepsze będzie Edge. W wypadku WebAssembly, które stało się po prostu częścią tych silników skryptowych (wykorzystywane są istniejący backend kompilatora, mechanizmy sandboksa, frontend ładowania modułów itp.) też różnice nie było wielkie.

Na łamach bloga Mozilli, pani Lin Clark opisuje jednak nowe metody ładowania i kompilowania kodu WebAssembly, które uczynią z Firefoksa 58 najszybszą platformę do uruchamiania takich aplikacji. Nie mówimy tu o wzrostach rzędu kilku-kilkunastu procent. Jak sprawdziliśmy na kompilacji nightly Firefoksa, mowa o kilkunastokrotnym przyspieszeniu. Innymi słowy, Mozilla zmieniła reguły gry. Nagle okazuje się, że kod w pliku .wasm jest przetwarzany szybciej, niż ładowany przez sieć, innymi słowy, przypomina bardziej bitmapowy obrazek niż kod w JavaScripcie. Do tej pory skrypt, który miał 100 KB, był uważany za duży skrypt, zaś obrazek mający 100 KB był w sumie niedużym obrazkiem. Teraz 100 KB skrypt WebAssembly musimy uznać również za nieduży.

Jak ten nowy kompilator działa? Przede wszystkim rozpoczyna kompilację wcześniej, nie czekając na pobranie całego pliku. Kompiluje kod WebAssembly linijka po linijce, jakby było to jakieś strumieniowane medium (właściwie to jest, za sprawą specjalnego interfejsu do strumieniowania kodu). A jako że sekcja kodu w pliku binarnym jest przed sekcją danych, kod do wykonania może być gotowy na długo przed pobraniem pliku. Tak pobierane obiekty .wasm mogą być oczywiście kompilowane równolegle.

Do tego dochodzą nowe techniki optymalizacji. Inżynierowie Mozilli mówią o warstwowym (tiered) kompilatorze. Gdy kod zaczyna napływać wartkim strumieniem, kompilowany jest przed podstawowy kompilator (Tier 1). Gdy kod ten zostaje uruchomiony i już działa, włącza się drugi kompilator, korzystający z zaawansowanych technik optymalizacji, w tle ponownie sobie kompiluje cały kod i podmienia pierwotną wersję na tę bardziej zoptymalizowaną. W efekcie aplikacja po chwili od uruchomienia przyspiesza.

Taki podział ma sens z perspektywy tego, co dostrzega użytkownik – kompilator Tier 1 jest dziesięciokrotnie szybszy od kompilatora Tier 2, a zarazem generuje dwukrotnie wolniejszy kod. Warto podkreślić, że oba kompilatory korzystają ze wszystkich dostępnych rdzeni procesora. Efekt można zobaczyć w prostym benchmarku Mozilli, mierzącym czas kompilacji gry Tanks.

To nie koniec możliwości optymalizacji: Mozilla zamierza w następnym etapie wprowadzić bufor dla już skompilowanego kodu. Po co ciągle kompilować te same pliki .wasm? Po otworzeniu strony internetowej z tym samym kodem, od razu dostaniemy w przeglądarce prekompilowany kod maszynowy. To naprawdę świt nowej ery dla zaawansowanych aplikacji webowych.

Użyteczne linki

Źródło fotografii Red fox with an egg – Flickr,
autor: Nora Feddal
licencja: CC BY-SA 2.0

© dobreprogramy

Komentarze