Wasmi 2.0: интерпретатор WebAssembly ускорился в 2,2 раза

Wasmi, это интерпретатор WebAssembly (Wasm) с открытым исходным кодом; его используют во встраиваемых устройствах (IoT), плагинных системах (Typst, Zellij, Josh), облачных хостах, смарт-контрактах (Soroban, Ripple) и даже в лёгких игровых консолях (Firefly Zero). После восьми месяцев работы над движком вышла версия Wasmi 2.0, релиз, сосредоточенный на производительности выполнения: по среднему геометрическому набора тестов wasmi-benchmarks на Apple M2 Pro Wasmi 2.0 работает примерно в 2,2 раза быстрее версии 1.0. Проект с октября 2024 года спонсирует Stellar Development Foundation (SDF).

В релиз попали и другие новшества, о которых просили пользователи: флаг сборки validate, заметно уменьшающий размер бинарного артефакта; стабильный учёт лимита выполнения (fuel metering); поддержка детерминированного профиля WebAssembly и улучшённый CLI-инструмент Wasmi.

Авторы сравнили Wasmi 2.0 с другими быстрыми переносимыми интерпретаторами Wasm, Wasm3, Stitch и Wasmtime Pulley, на трёх аппаратных платформах: Apple M2 Pro, AMD EPYC 7763 и Intel Xeon Platinum 8370C. По их выводу, Wasmi 2.0 уверенно входит в категорию самых быстрых переносимых интерпретаторов Wasm. При этом, несмотря на упор на производительность выполнения, скорость запуска (startup) осталась на уровне версии 1.0.

В основе ускорения, новая архитектура диспетчеризации инструкций. Wasmi 2.0 поддерживает четыре режима диспетчеризации; в статье подробно описаны три: Direct-Threaded Code, для максимальной производительности, Indirect-Threaded Code, компромисс между скоростью и потреблением памяти, и Switch-Loop, для платформ без поддержки хвостовых вызовов (tail calls). Функция сборки auto-dispatch автоматически выбирает конфигурацию на основе threaded-code там, где это возможно.

Каждый обработчик инструкции в Wasmi 2.0 принимает девять аргументов, и 7 из 9 должны передаваться через регистры общего назначения (GPR). Но стандартные соглашения о вызовах вроде sysv64 дают под целочисленные аргументы только до 6 GPR, седьмой аргумент пришлось бы каждый раз сохранять в стек, что снижает производительность. Для сравнения, Stitch обходится 6 регистрами, а Wasm3, четырьмя. Wasmi решил проблему, переведя один из аргументов (instance) в регистр с плавающей точкой; по данным авторских бенчмарков, такой перенос между регистровыми доменами почти не влияет на скорость.

Главное архитектурное изменение, три новых аккумуляторных регистра (ireg, freg32, freg64), заменившие подход версии 1.0, где операнды и результаты инструкций хранились в ячейках стека. Теперь инструкции читают и пишут значения напрямую в регистры процессора, без декодирования и загрузки из стека, это и даёт основной прирост скорости. Обратная сторона: результат инструкции по умолчанию остаётся в аккумуляторе, и если следующая инструкция должна использовать другой аккумулятор, значение нужно явно скопировать в ячейку стека, таких копирующих инструкций в Wasmi 2.0 стало больше, чем в версии 1.0. Команда снижает их число двумя способами: набором оптимизированных инструкций копирования для типовых случаев и слиянием опкодов (op-code fusion), объединением часто идущих подряд операций (например, сложения или загрузки с последующим local.set/local.tee) в один обработчик. Текст источника обрывается на описании слияния опкодов, поэтому итоговые результаты этой оптимизации в материале не приведены.

Ключевые факты

  • Wasmi 2.0 вышел после восьми месяцев переработки движка и в среднем геометрическом на 2,2 раза быстрее версии 1.0 (тесты wasmi-benchmarks, Apple M2 Pro).
  • Проект с октября 2024 года спонсирует Stellar Development Foundation; Wasmi используют в плагинных системах (Typst, Zellij, Josh), смарт-контрактах (Soroban, Ripple) и консоли Firefly Zero.
  • Ключевое изменение, три новых аккумуляторных регистра (ireg, freg32, freg64) вместо хранения операндов в ячейках стека, как было в версии 1.0.
  • Из 9 аргументов обработчика инструкции 7 требуют регистров общего назначения, а соглашение вызова sysv64 даёт только до 6, Wasmi обходит лимит, переводя один аргумент в регистр с плавающей точкой.
  • Wasmi 2.0 поддерживает четыре режима диспетчеризации инструкций (в статье раскрыты три), стабильный учёт лимита выполнения (fuel metering) и детерминированный профиль WebAssembly.

Почему это важно

Разработчики WebAssembly-рантаймов регулярно упираются в потолок производительности классических интерпретаторов с байткодом на стеке. Wasmi 2.0 показывает конкретный путь его пробить: перейти от хранения операндов в стеке к аппаратным аккумуляторным регистрам и подобрать режим диспетчеризации инструкций под конкретную платформу. Прирост в 2,2 раза, заметный результат для зрелого open-source проекта, а не разовый бенчмарк-трюк: он получен на полном наборе тестов и на нескольких архитектурах процессоров.

Кому это важно

В первую очередь, разработчикам, которые встраивают WebAssembly в свои системы: авторам плагинных платформ вроде Typst, Zellij и Josh, команде смарт-контрактных платформ Soroban и Ripple, разработчикам IoT-устройств и облачных хостов, а также создателям лёгких игровых консолей вроде Firefly Zero. Отдельно материал полезен инженерам, которые сами пишут интерпретаторы байткода на Rust или других системных языках, статья подробно разбирает архитектурные решения на уровне регистров и вызовов функций.

Как это применить

Командам, уже использующим Wasmi, стоит обновиться до 2.0 и включить функцию сборки auto-dispatch, которая сама подберёт наиболее быстрый режим диспетчеризации, поддерживаемый платформой. Для сборок с жёстким ограничением по размеру бинарника пригодится флаг validate, а проектам, которым нужен предсказуемый лимит выполнения (например, смарт-контрактам), стабилизированный в этой версии механизм fuel metering. При портировании на платформы без поддержки хвостовых вызовов нужно явно выбрать режим Switch-Loop вместо Direct-Threaded Code.

Можно ли доверять

Материал, официальный пост автора и мейнтейнера Wasmi в блоге проекта, а не независимый обзор. Ключевая цифра (2,2 раза) получена на собственном наборе тестов wasmi-benchmarks, который, по словам автора, можно воспроизвести самостоятельно, это снижает риск манипуляции результатами, но сравнение всё равно сделано разработчиком своего же продукта. С октября 2024 года проект спонсирует Stellar Development Foundation, у спонсора есть финансовый интерес в развитии Wasmi, хотя источник не раскрывает подробностей этого партнёрства. Сам текст источника обрывается до раздела с выводами, поэтому часть материала, в частности, итоги по слиянию опкодов, для пересказа недоступна.

Риски и подводные камни

Главные оговорки: цифры даны только автором проекта на собственных тестах, без стороннего аудита, а точные результаты по отдельным платформам представлены в исходном материале как графики, а не как таблица чисел. Заявлено четыре режима диспетчеризации, но подробно разобраны только три, какой ещё режим существует, из доступного текста не следует, поэтому это не переносится в пересказ. Новая регистровая архитектура меняет одну проблему на другую: она ускоряет выполнение, но требует больше вспомогательных инструкций копирования между аккумулятором и стеком, а статья обрывается ровно на разделе, где команда описывает, насколько успешно ей удалось этот прирост числа инструкций компенсировать.