Tworzę na własne potrzeby interpreter Z80 bazując na swoim starym projekcie https://github.com/andrzejlisek/ScriptSDCC/tree/master/src
Robię to w technice WebAssembly, wykorzystując do tego Emscripten.
Wszystko fajnie, pięknie i nie byłoby żadnego problemu, gdyby nie pewna sprawa wydajnościowa.
W nowym projekcie wykorzystuję między innymi te pliki z niewielkimi zmianami:
https://github.com/andrzejlisek/ScriptSDCC/blob/master/src/scriptmachinez180.h
https://github.com/andrzejlisek/ScriptSDCC/blob/master/src/scriptmachinez180.cpp
https://github.com/andrzejlisek/ScriptSDCC/blob/master/src/scriptmachine.cpp
https://github.com/andrzejlisek/ScriptSDCC/blob/master/src/scriptmachine.h
Tak naprawdę, to tworzę obiekt klasy ScriptMachineZ180 dziedziczący po ScriptMachine.
W ramach testów, wczytuję jakiś prosty program na Z80 do maszyny skryptowej Z180 i wykonuje w pętli "DoCommand" po 10000000 razy. Po prostu zwykła logika obliczeniowa, która operuje na jednej tablicy mającej 64k elementów, nic szczególnego się nie dzieje.
Rzecz wygląda następująco:
- Najpierw zrobiłem zwykły projekt w Qt Creator, który wykorzystuje te pliki na potrzeby prób i testów, żeby w ogóle ruszyć i ułatwić dostosowywanie pod nowy projekt. Test trwa ok. 0,3 sekundy.
- Potem w Firefox kompiluję za pomocą Emscripten wykorzystując identyczne pliki. Dokładnie ten sam kod i ten sam test wykonuje się przez ok. 1 sekundy. Tą przeglądarkę używam codziennie.
- W Chrome, identyczna binarka, identyczna czynność trwa już prawie 5 sekund. Na co dzień z niej nie korzystam, nawet często czyszczę w niej historię i cookiesy, bo nie mam nic do stracenia, a czasami chcę coś sprawdzić na czysto.
Z czego wynikają te różnice? Kompilację do Wasm wykonuję takim poleceniem (dla przejrzystości nie wymieniłem wszystkich plików źródłowych):
emcc -std=c++20 prog/scriptmachine.cpp prog/scriptmachinemcs51.cpp prog/scriptmachinez180.cpp progcore.cpp -s BUILD_AS_WORKER=1 -o compiled/progcore.js -s ALLOW_MEMORY_GROWTH
Identyczne polecenie wypracowałem przy tworzeniu innego projektu, a sama logika obliczeniowa jest wyniesiona do Worker
, tak, że z JavaScript uruchamiam polecenie Workera, wykonuje się obliczenie i do JavaScript wraca wynik. Sama implementacja jest w taki sposób: https://stackoverflow.com/questions/32291084/minimal-working-example-for-emscripten-webworker
Ten sam kod, a tak duża różnica wydajnościowa i to między przeglądarkami. Czytałem też o kompilatorze Cheerp, z jednej strony on ponoć wytwarza lepszy i wydajniejszy kod od Emscripten, ale z drugiej strony ma bolączki takie, że nie ma "prawdziwych" wątków na wzór pthreads
(akurat w tym projekcie niepotrzebne, ale w innym potrzebne) i nie ma też natywnie obsługiwanego przekazywania tekstu jako parametr funkcji i wyciągania tekstu jako wartości zwracanej, i to zarówno przy wywoływaniu C++ z JavaScript, jak i przy wywoływaniu JavaScript z C++. Da się to zapewne jakoś ominąć, ale to byłoby rozwiązanie karkołomne. A i tak, zanim doszedłem, co i jak z Emscripten, jak to zrobić, jak to działa, to zeszło dwa, trzy dni na czytaniu dokumentacji i próbowaniu tworzenia programu.
Czy zamieniając Emscripten na Cheerp, jest szansa na zyskanie dwukrotnego, czy trzykrotnego przyspieszenia? Bo jeżeli maksymalne przyspieszenie, to byłoby może o 20% przy dobrych wiatrach, to dla mnie nie ma sensu zawracać sobie tym głowy i zostaję przy Emscripten.