Web Worker – Számolni? Böngészőben?!
Kezdetben teremtetett vala a karakteres konzol és a HTML dokumentum. De a konzol sötét (hátterű) vala, ezért lőn világosság és grafikus felület. A HTML dokumentum azonban kietlen és puszta maradt. Hogy a dokumentumnak szépséget adjon, teremté a Programozó a CSS-t, s hogy funkcionalitást és interaktivitást adjon neki, teremté a JavaScriptet. És látá a Programozó, hogy ez jó.
Idővel felnövekedék a Programozó Fia, és feltette a kérdést: „Apám! A JavaScript egy programozási nyelv?” „Igen”, szólt a válasz. „De Apám! Akkor miért nem írunk programot JavaScriptben?” „Fiam – felelt az Apa –, a JavaScriptet nem erre találták ki.” A Fiút azonban nem hagyta nyugodni a gondolat. Miért tart itt a világ?
A jelenlegi helyzet két okra vezethető vissza: egyrészt a JavaScript egy köztudottan lassú nyelv. És ez a tapasztalat annak ellenére, hogy a böngészők gyártói versenybe hajszolták egymást, egyre gyorsabban teljesítik a kifejezetten JavaScript számára írt benchmarkokat, még az Internet Explorer 9 is azzal dicsekszik, hogy gyorsabb lesz, mint a Firefox. No, de mit mutatnak a száraz tények? Keressük meg a 35. fibonacci számot:
function fibonacci(n) {
return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
var start = new Date();
var fib = fibonacci(35);
var end = new Date();
alert((end - start) / 1000.0 + ' (' + fib + ')');
A fentivel lényegében egyező a kódot a különböző nyelveken futtatva a következő eredményt kaptam (futásidő másodpercben):
Nem várt eredmény: azonos gépen futtatva a szerver oldali nyelvek közül csak a C, a Java és a JavaScript (Node.js) képes felvenni a versenyt a modern böngészők JavaScript sebességével. (Valószínűleg a .NET is, ezt nem volt lehetőségem tesztelni. Az IE9 előzetes az IE8 és a Firefox között van, a használt gépen nem állt rendelkezésemre.) Igaz, ez a teszt erősen épít a függvényhívásokra. Számítási műveleteket végezve nincsenek ekkora különbségek, de a legelterjedtebb szerver oldali nyelvnél a legelterjedtebb alternatív böngészők így is nagyságrendekkel gyorsabbak:
function isPrime(num) {
for (var i = 2; i <= Math.sqrt(num); i += 1) {
if (num % i == 0) {
return false;
}
return true;
}
}
var num = 123456789, count = 0, start, end;
start = new Date();
while (count < 3000) {
if (isPrime(++num)) {
++count;
}
}
end = new Date();
alert((end - start) / 1000.0 + ' (' + num + ')');
Természetesen e két kódból nem lehet messzemenő következtetéseket levonni, de megvan annak a lehetősége, hogy kliens oldalon hamarabb végzünk.
Azonban van itt egy bökkenő, és ez a nyomósabb ok, ami miatt nem írunk komolyabb számítási feladatot JavaScriptben: a HTML oldal megjelenítése alapvetően egy szálon fut, így amíg számítást végzünk, semmi más nem történhet a böngészőben. A klasszikus JavaScript programban meghívunk egy függvényt, megkapjuk az eredményét és megjelenítjük: showResult(i, countPrimes(from, to));
Megszámoljuk, hogy hány darab prímszám található from
és to
között, majd az i
-edik cellában jelezzük, hogy kész.
A mellékelt oldalon a Classic gombra kattintva elkezd számolni a böngésző, és (kellően nagy szám esetén) az animáció megáll, de még a gomb is benyomva marad. A számolás végeztével láthatjuk az eredményt, és minden megy tovább – de addigra a felhasználó rég kiábrándult belőlünk. Ha a közben valamilyen módosítást végzünk a DOM-on, az is bekerül a várakozási sorba. Ebben jelenleg egyedüli kivétel az Opera: bár itt is megáll az animáció, de a DOM-on végzett módosítások azonnal megjelennek. A jövőben a WebKit2 alapú böngészők is így fognak viselkedni.
Ezt a valóban hátrányos viselkedést kiküszöbölendő a Google a Gears részeként biztosította a WorkerPool használatának lehetőségét, aminek a bemutatására nem térnék ki, hiszen ezt szabványosítva alkotta meg a WHATWG a HTML5 ajánlás részeként a Web Worker API-t, melynek segítségével háttérfolyamatokat indíthatunk a böngészőben. Egy folyamat üzenetküldéssel kommunikálhat a szülőjével és a gyermekeivel, osztott memória használatára nincs lehetőség. Workert elindítani a forrásfájl megadásával lehetséges, beállíthatjuk, hogy mi történjen üzenet vagy hiba esetén, és természetesen küldhetünk üzenetet (bemutató mellékelve):
var worker = new Worker('hello.js');
worker.onmessage = function(event) {
alert('Message: ' + event.data);
};
worker.onerror = function(event) {
alert('Error: ' + event.message );
};
worker.postMessage('world');
A forrásfájl sem bonyolultabb:
onmessage = function(event) {
var name = event.data;
setTimeout(function() {
postMessage('Hello ' + name + '!');
}, 1000);
throw 'Something happened...';
};
A worker oldalon lehetőségünk van további forrásfájlok betöltésére az importSrcipts(file1, file2, …)
paranccsal. Erre szükségünk is lesz, hiszen a worker nem látja a szülő folyamatban betöltött moduljainkat. Sőt, mivel itt nem végezhetünk semmilyen DOM műveletet, window
és document
objektumunk sincs. Ellenben van XMLHttpRequest
, így a hálózati kommunikáció rendelkezésünkre áll.
Böngészők: jelenleg a Chrome, a Firefox és a Safari támogatja teljes mértékben. A szabvány szerint szöveges üzeneteket küldhetünk, Firefox esetén bármi mást is, amit JSON-ná tud alakítani. Az Opera és az Internet Explorer nem támogatja, ez utóbbi esetben lehetséges a WorkerPool használata, azonban a két API nem kompatibilis egymással.
Workereket, és a workeren belül újabb workereket elméletileg korlátlan számban indíthatunk, a Mozilla MDC oldalán Fibonaccit számolunk, a rekurzív hívás helyett újabb worker indításával. (A cikk megírásának pillanatában csak a Firefox alatt lehetet workeren belül újabb workert indítani.)
Felmerül még a skálázhatóság kérdése is. Google Chrome esetén minden worker saját processzt kap, Safarinál saját szálon fut, Firefoxban ezt másképpen oldották meg, nem indulnak új szálak (Windows alatt). Emiatt Chrome és Safari esetén megfelelő tervezéssel négymagos gépen négyszer olyan gyorsan számolhatunk. Erről Dennis Forbes írt egy jó cikket. Szintén ő készített egy demó oldalt, amelyen a SunSpider benchmarkokat végezhetjük el workerek segítségével.
De mit is ér a workerben lévő lehetőségeket kihasználó kód, ha az Internet Explorerben nem fut, vagy csak nagyon lassan, és a böngészőt blokkolva végez? Ilyen tekintetben meg kell különböztetünk a weboldalakat és a webalkalmazásokat. Weboldalaknál jelenleg sem végzünk komoly számításokat, még szerver oldalon sem, hiszen ez hamar túlterhelést okozna. A workerek segítségével támogató böngésző esetén átvehetünk egyes feladatokat, úgy, hogy a nem támogató böngészővel érkezőknek nem romlik az élményük. (És talán váltani is fognak, ha a fejlesztő számára kényelmesebb böngésző használata számukra is előnyökkel jár.) Ám nem szabad megfeledkezni arról, mi van, ha a kliens egy netbookkal jön: ha hússzor gyorsabb a szerver alatt a vas, és kétszer lassabb az alkalmazott nyelv, akkor tíz felhasználó párhuzamos kiszolgálása esetén már gyorsabb, ha a kliens oldalon számolok (nem beszélve a hálózati kommunikáció miatti késleltetésről). Alkalmazásoknál megtehetjük, hogy a felhasználót megkérjük a futtató környezet telepítésére – ahogyan Java vagy .NET esetén –, portable böngészők használatával még rendszergazdai jog sem kell hozzá.
(A cikk ikonjához a NIOSH Construction workers not wearing fall protection equipment c. fotóját használtuk fel.)
■
XUL
Nem fog
Valóban nincsen osztott memória terület. De ha a valóban számításigényes kódot teszed át egy (néhány) worker-be, valószínű, hogy az üzenet küldés nem okoz nagy overhead-et, így a szükséges adatokat mindig megkaphatod a szülőtől. (Egyébként szerintem is kényelmesebb lenne, ha legalább olvasási elérhetőség lenne…)
A SharedWorker segítségével majd egy workerhez több „szülő” is csatlakozhat. (Tudtommal jelenleg egy böngésző sem támogatja.)
Nem megy
Hasznos
- gyorsabb is a workerben futtatott kód, vagy "csak" független?
- az iframe-ben futtatott kód is megállítja a szülőablak folyamatait?
- nehéz lenne-e egy olyan illesztőt írni, amelyik az adott kódot workerben futtatja, ha lehetséges, és ha nem, akkor workerpoolt használ, vagy ha az sem, akkor csak simán elindítja (esetleg egy iframe-ben) (?)
(Ezeket a kérdéseket magamnak tettem fel, kipróbálgatom nemsokára)
Válaszok
- Blokkolja. Firefox-nál még a teljesen függetlenül megnyitott másik lapot is.
- Attól függ, mennyire akarsz kompatibilis lenni a különböző APIkkal. A JSWorker ezt akarta megvalósítani, de saját API-t vezetett be, és abbahagyták a fejlesztését. Az IE worker csak emulálná, de az importScripts fájlokat a globális névtérbe teszi. Én is készítek egyet, ez már jobb az iframe-es dolgokban, de még fejlesztés alatt áll.
A Google azt javasolta, hogy használjuk inkább a HTML5 ajánlásait, mint a Gears-t (persze, Chrome-ban, ahol mindkettő működik jelenleg). Nem tudom, mennyire elterjedt az IE + Gears kombó, megéri-e vele foglalkozni…
Köszönöm
Nagyon köszönjük!
Nagyon hasznos leírás volt. Köszönjük!
üdv, Gábor
u.i: Reméljük mihamarabb támogatni fogja az Explorer is.. az IE22 már egész biztosan.. :)
1-2 ötlet
amíg számítást végzünk, semmi más nem történhet a böngészőben
Ez így nem teljesen igaz, meg lehet setTimeout-al oldani a multi thread-et, vagy akár async ajax-al (problémától függően). Persze ez azért közel sem ugyanaz, mint java-ban, ahol készen kapja az ember. (Közben próbálgattam több módszerrel is, de tényleg behal tőle a böngésző :-) visszaszívtam :D)
Nem várt eredmény: azonos gépen futtatva a szerver oldali nyelvek közül csak a C, a Java és a JavaScript (Node.js) képes felvenni a versenyt a modern böngészők JavaScript sebességével.
Javascript-nek egyébként a nagy számokkal elég komoly problémái szoktak lenni... Lehet, hogy kevesebb memóriát foglalnak le a számok, ezáltal kisebb a stack, ahová másolja őket, és ezért gyorsabbak a függvények.
(Olyan nagyon mondjuk nem folytam bele a js belső működésébe, szóval ez csak találgatás...)
multiThread
Nézzük a következő kódot:
A Worker-ekben point az az izgalmas, és hasznos, hogy mindegyik külön szálon fut, azáltal nem blokkolja az aktuális kód futását, mint ahogy normál esetben a JS tenné.
Igen
Számok
Nem tudom, tudod-e, de JavaScript-ben egyfajta szám (Number) létezik, és az a hagyományos nyelvekben előforduló Double, vagyis 64 bites lebegőpontos szám. Ezzel a mai számítógépek nagyon gyorsan tudnak számolni, még gyorsabban is általában, mint az egész számokkal.
Másik dolog, ami gyorssá teszi a számokkal való műveleteket, az az, hogy nem kell a különböző szám típusok között konvertálgatni, hanem mivel egyfajta számtípus van, nincs szükség átalakításra.
Nem tudtam