2022-08-07

vak: (Default)
При прочих равных я всегда предпочитаю родные бинарники. Серьёзная программа должна выполняться в машинных кодах, как предписано разработчиком процессора. Виртуальных машин избегаю по возможности. Они обычно только вносят лишнюю сумятицу.

К примеру, Питон, отличный язык, но построенный вокруг специальной виртуальной машины. Вопрос на засыпку: сколько разных копий Питона установлено на вашем компьютере? Я глянул на своём маке:
  • собственно Питона установлено четыре версии: 2.7, 3.8, 3.9, 3.10
  • копия Питона 3.7 внутри приложения LibreOffice
  • три лишних копии Питона 3.8 в приложениях XCode, Apple CLI Tools и KiCad
  • ещё одна копия Питона 3.9 в компиляторе для ESP32
  • плюс десять копий Питона 3.10 в утилитах awscli, bpytop, gallery-dl, glances, meson, scons, sip, sphinx-doc, xonsh, youtube-dl
Итого 19 штук одного Питона. Отдельно можно посчитать Java, .NET, Ruby, Guile, Lua и другие. Налицо нездоровая тенденция виртуальных машин плодиться и замусоривать пространство. Версии плодятся, и чтобы избежать проблем несовместимости каждый софтверный продукт стремится тащить с собой отдельную копию каждой виртуальной машины. 
vak: (Default)
На виртуальные машины я пожаловался, теперь глянем на дело конструктивно. Предположим, мы хотим сделать софт, которому суждена долгая жизнь. Скажем, декодер звука MP3, или отрисовщик документов в формате DJVU, или парсер санскрита. Лет через сто какой-нибудь марсианин захочет прослушать или прочитать древний файл из архива землян. Понятно, что бинарники за сто лет устареют. А виртуальные машины выживут (не все), и могут спасти ситуацию. Тут я задумался. Какие из современных вариантов имеют лучшие шансы? И сдаётся мне, что самая распространённая нынче и живучая виртуальная машина это JavaScript. В каждом компьютере установлен как минимум один браузер, и в нём эта машина. Можно было бы на неё положиться.

Возникает вопрос: каким образом можно разрабатывать софт для запуска на VM внутри браузеров? Ответ состоит в магическом слове WebAssembly. Это стандарт, Draft Release 2.0 на настоящее время, определяющий бинарный формат и софтверные интерфейсы. Существующие компиляторы Си/Cи++ (clang) и Rust умеют генерить бинарный код для Wasm.

Отлично! С чего же начать? В сети есть куча примеров, как скомпилировать Си-код, вычисляющий функцию Фибоначчи, и вызвать её из браузера. Но этот не то, чего хочется. Вот есть у меня симулятор древней ЭВМ БЭСМ-6. Как скомпилировать его для Wasm, и вызывать как обычно, из командной строки? Ведь виртуальная машина браузера изолирует ваш код от окружения и мало чего ему позволяет. Оказывается, умные люди об этом уже позаботились. Встречайте проект WASI: интерфейс из WebAssembly к сервисам локальной операционной системы.

Компилируем

Меньше слов, больше дела. Устанавливаем компилятор Си/Си++ для WASI отсюда: wasi-sdk-16

Компилируем простейший код:
$ cat hello.c
#include <stdio.h>
int main()
{
printf("Hello, WebAssembly!\n");
}

$ /opt/wasi-sdk-16.0/bin/clang hello.c -o hello.wasm

$ file hello.wasm
hello.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

$ /opt/wasi-sdk-16.0/bin/size hello.wasm
text data bss dec hex filename
14817 2651 0 17468 443c hello.wasm

Выполняем

Теперь выполним этот бинарник. Необязательно звать весь браузер, есть решение попроще. Устанавливаем утилиту wasmtime, по сути независимую реализацию виртуальной машины WebAssembly. На маке она ставится так:
brew install wasmtime
Установка на Линуксе:
curl https://wasmtime.dev/install.sh -sSf | bash
Теперь запускаем наш пример:
$ wasmtime hello.wasm 
Hello, WebAssembly!
В следующий раз попробую запустить бенчмарк Dhrystone.
vak: (Default)
В продолжение темы: меряем эффективность WebAssembly. Берём известный тест Dhrystone и компилируем для WASI:
$ make
/opt/wasi-sdk-16.0/bin/clang -O -DNRUNS=100000000 -c -o dhry_1.o dhry_1.c
/opt/wasi-sdk-16.0/bin/clang -O -DNRUNS=100000000 -c -o dhry_2.o dhry_2.c
/opt/wasi-sdk-16.0/bin/clang -o dhrystone dhry_1.o dhry_2.o

$ file dhrystone
dhrystone: WebAssembly (wasm) binary module version 0x1 (MVP)
Запускаем:
Nanoseconds for one run through Dhrystone: 35.3    
Million Dhrystones per Second: 28.331
DMIPS: 16124.6
Для сравнения скомпилим в родной бинарник:
$ make
cc -O -DNRUNS=100000000 -c -o dhry_1.o dhry_1.c
cc -O -DNRUNS=100000000 -c -o dhry_2.o dhry_2.c
cc -o dhrystone dhry_1.o dhry_2.o

$ file dhrystone
dhrystone: Mach-O 64-bit executable x86_64
Запускаем:
Nanoseconds for one run through Dhrystone: 25.4    
Million Dhrystones per Second: 39.351
DMIPS: 22396.8
Получаем скорость 16124.6 на Wasm по сравнению с 22396.8 на родном Интеле. То есть потеря 28% производительности: неплохо! Вполне терпимо для для виртуальной машины такой сложности.