Применяется к телеметрии космических аппаратов ЕКА.

Оглавление

Часть 1
Введение
Стек разработки
Тесты производительности

Часть 2
Дополнительные факторы, влияющие на производительность
Опыт разработки
Выводы

1. Введение

Популярность WebAssembly (WASM) резко возросла за последние несколько лет из-за его основного обещания — возможности запуска кода, написанного на нескольких языках, в Интернете с почти родной скоростью. Такие возможности звучат очень интересно при выборе стека разработки для нового веб-приложения, особенно когда основной целью является предоставление ресурсоемких функций.

В этой статье мы начнем с изучения технологии WASM и анализа ее производительности, уделяя особое внимание различным типам вычислений и обработки данных в различных условиях среды. Далее мы сравним результаты производительности с эквивалентом JavaScript. Наконец, мы рассмотрим реальный пример, который мы разработали в Европейском космическом агентстве (ЕКА), перенося некоторые функции обработки телеметрии космического корабля, которые в настоящее время выполняются на сервере, в веб-клиент.

Поскольку WebAssembly не является языком, на котором инженеры-программисты пишут код напрямую, существует несколько языков программирования низкого и высокого уровня, которые компилируются в WASM. Rust был признан самым любимым языком программирования с 2015 года ([1][2]), и, поскольку он компилируется в WASM, мы выбрали его для этого исследования.

2 Стек разработки

2.1 Сравнение языков программирования

Нет никаких сомнений в том, что Rust и JavaScript — очень разные языки программирования. Вкратце: Rust — это скомпилированный, низкоуровневый, высокооптимизированный язык программирования с нулевой стоимостью, который также утверждает, что он очень безопасен благодаря надежной системе типов и очень уникальному управлению памятью. С другой стороны, JavaScript — это динамичный язык сценариев высокого уровня, который обычно компилируется точно в срок и предлагает богатую экосистему библиотек и фреймворков. [3][4]

Как упоминалось ранее, Rust — это язык программирования общего назначения, который обычно компилируется в собственный код; однако он также был в авангарде веб-разработки, предлагая цель компиляции WASM, которая позволяет запускать его непосредственно в веб-браузере. [3]

Хотя и Rust, и JavaScript предлагают богатую экосистему библиотек и фреймворков через соответствующие менеджеры пакетов, у JavaScript в этом отношении есть преимущество. Поскольку JavaScript широко распространен и используется, он предлагает гораздо большую экосистему библиотек и фреймворков, чем Rust. Многие из этих веб-фреймворков, например Angular или React, поддерживаются такими крупными компаниями, как Google и Meta. К сожалению, в Rust нет такой экосистемы, по крайней мере, в веб-пространстве. Однако это может измениться с постоянно растущей популярностью Rust и его принятием крупными технологическими компаниями в ближайшие годы. [5] [6]

2.2 Веб-фреймворки

Когда люди говорят о Интернете, они часто имеют в виду отображение контента на веб-странице. В то время как JavaScript уже давно является игроком в этой области и, согласно Википедии, используется на 97% всех веб-сайтов, WASM — относительно новая технология, которая все еще находится в зачаточном состоянии [4].

Один ключевой элемент, который на сегодняшний день отсутствует в WASM, — это прямое манипулирование DOM. Вместо этого для доступа к большинству веб-API код WASM должен вызывать функцию JavaScript, отвечающую за обновления DOM, что увеличивает сложность и вызывает снижение производительности. Было несколько предложений изменить это, но до сих пор ни одно из них не было реализовано.

2.3 Тис против Реакта

Несмотря на то, что WASM в настоящее время несколько ограничен, появилось несколько веб-фреймворков. Одним из самых популярных на данный момент
является Тис [7]. Yew — это фреймворк, построенный на основе технологии WASM и использующий Rust в качестве языка программирования.

В этом исследовании мы решили сравнить Yew с React, поскольку в настоящее время это самый популярный фреймворк JavaScript [8].

Концептуально Yew очень похож на React. Обе платформы имеют компонентную архитектуру, позволяющую создавать многократно используемые компоненты. В фоновом режиме обе платформы используют виртуальные модели DOM, которые обнаруживают изменения и применяют их к реальной модели DOM.

3 теста производительности

Исходя из характера этих двух технологий и их различий, мы разделили тесты производительности на три группы. Первая группа будет посвящена обновлениям HTML DOM; следующая группа будет посвящена арифметическим операциям, выполняемым в фоновом режиме; последняя группа будет посвящена обработке данных, в нашем случае это будет обработка телеметрии космического корабля.

3.1 Управление HTML DOM

Чтобы сравнить производительность Yew и React, мы внедрили базовый визуальный двоичный счетчик в обоих фреймворках. Затем мы измерили время, необходимое для рендеринга содержимого HTML в обеих средах, и наблюдали за потреблением памяти.

Чтобы увеличить нагрузку на фреймворк, мы добавили возможность настраивать количество отображаемых бинарных счетчиков. С помощью этого бенчмарка мы хотели протестировать виртуальный DOM фреймворка и возможность эффективно вносить изменения в DOM.

На рисунке 2 показано, что изначально платформа React может обновлять двоичные счетчики (модифицировать HTML DOM) значительно быстрее, чем Yew. Запустив тест несколько раз, мы смогли последовательно наблюдать, что React быстрее, чем Yew, в 2 раза. Однако увеличение количества счетчиков показывает, что производительность React значительно падает. В то же время производительность Yew более постоянна.

На рис. 3 показано, что Yew может выполнять больше вызовов рендеринга в секунду, чем React. React достигает пика около 4000 вызовов рендеринга в секунду, в то время как Yew продолжает увеличивать количество вызовов рендеринга с увеличением двоичных счетчиков, которые ему приходится обрабатывать.

Рисунок 4 показывает, насколько значительна разница в потреблении памяти между Yew и React. Во-первых, приложение в React требует примерно в пять раз больше памяти, чем приложение в Yew при загрузке страницы. В обеих средах потребление памяти неуклонно увеличивается с увеличением количества двоичных счетчиков. Однако память для каждого двоичного счетчика немного больше для приложения React, что приводит к более высокому потреблению памяти в целом. Панели React также показывают сборщик мусора (GC) в действии — обратите внимание, как сборщик мусора JavaScript в какой-то момент освободил часть памяти (после запуска 20 двоичных счетчиков, см. рис. 4). С другой стороны, Yew в полной мере использует механизмы управления памятью Rust, начиная с очень низкого потребления памяти и постепенно увеличивая его с увеличением количества двоичных счетчиков.

Зная, что обе платформы используют Virtual DOM, мы ожидаем, что их реализация вносит наибольший вклад в производительность. Хотя Yew платит штраф при обновлении DOM (путем вызова JavaScript), что особенно заметно при попытке контролировать только несколько двоичных счетчиков, числа, которые мы получили при выполнении наших тестов, показывают, что реализация Virtual DOM в Yew лучше оптимизирована, чем Реагировать один. Этот вывод становится еще более очевидным, если взглянуть на данные о потреблении памяти. Благодаря управлению памятью Rust приложение, написанное на Yew, потребляет значительно меньше оперативной памяти с самого начала на протяжении всего жизненного цикла приложения, а также ему не нужно совершать вызовы GC.

3.2 Арифметические операции

Следующий тип теста производительности фокусируется на арифметических операциях. Мы реализовали арифметические функции полностью изолированным образом, чтобы избежать загрязнения результатов тестирования разного рода вспомогательными операциями, влияющими на измерение производительности (например, манипулирование HTML DOM). Алгоритмы были написаны таким же идиоматическим образом, чтобы гарантировать правильность и справедливость результатов тестирования.

3.2.1 Вычисление простого числа

Первый эталон — это идиоматический алгоритм, проверяющий, является ли заданное число простым. Измеренное время предназначалось для вычисления того, является ли число 25 964 951 простым (что так и есть).

В листинге 1 показана реализация тестового кода для Rust/WASM, а в листинге 2 — реализация на JavaScript.

Удивительно, но JavaScript превосходит Rust почти вдвое быстрее. Этот результат был неожиданным, поскольку WASM обещает почти родную скорость, а использование скомпилированного языка более низкого уровня теоретически должно работать намного лучше, чем любой язык сценариев.

3.2.2 Функция Аккермана

Функция Аккермана известна как один из первых открытых примеров тотальной вычислимой функции, которая не является примитивно-рекурсивной [9]. Что важно для нас в этом тесте, так это то, что этот алгоритм широко использует рекурсию. Мы хотим провести стресс-тест вычислительной производительности с помощью рекурсии.

В листинге 3 показана реализация тестового кода для Rust/WASM, а в листинге 4 — реализация на JavaScript. Версии Rust и JavaScript немного отличаются друг от друга в реализации, чтобы представить идиоматический способ реализации этого алгоритма разработчиком на данном языке.

На рис. 6 показан явный выигрыш WASM на этот раз. Что здесь примечательно, так это то, что Firefox, кажется, может извлечь больше выгоды из реализации WASM, чем Chrome. Хотя в Chrome функция Акермана в JavaScript работает быстрее, чем в Firefox, в версии WASM все наоборот. Обеспечивает увеличение производительности в Firefox в 4,75 раза по сравнению с JavaScript и увеличение в 2 раза по сравнению с той же версией WASM в Chrome.

Мы запустили тест с входными числами 3 и 9 (т. е.: ackermann(3, 9)), поскольку все, что выходит за эти значения, приводило к сообщению об ошибке браузера «слишком много рекурсии» как в JavaScript, так и в WASM. .

Примечание: изменение реализации Rust на if else не меняет результаты. Компилятор скомпилирует тот же код WASM.

3.2.3 Последовательности Коллатца

Конъюнктура Коллатца, также известная как 3n + 1 проблема, является одной из самых известных нерешенных проблем в математике.[10] Гипотеза спрашивает, превратит ли повторение двух простых арифметических операций каждое положительное целое число в 1.[10] Несмотря на то, что проблему Коллатца можно решить идиоматическим способом с помощью рекурсии, мы отказались от этого, поскольку уже протестировали рекурсию с помощью функции Аккермана и искали разнообразную вычислительную задачу, которую трудно оптимизировать.

В листинге 5 показана реализация тестового кода для Rust/WASM, а в листинге 6 — реализация на JavaScript.

В этом тесте мы видим самую большую общую разницу между WASM и JavaScript, где WASM превосходит JavaScript более чем в 10 раз в Chrome. Примечательно, однако, что оба результата для JavaScript и WASM в Firefox противоречат результатам предыдущей функции Аккермана. На этот раз Chrome кажется медленнее в выполнении JavaScript, но значительно выигрывает от версии WASM, в результате чего скорость выполнения WASM более чем в 2,5 раза выше, чем в версии Firefox.

3.2.4 Выводы

Первоначально результаты этого набора тестов производительности были очень запутанными (или, мягко говоря, удивительными). Некоторые алгоритмы были либо очень быстрыми в WASM, либо очень медленными в JavaScript, либо наоборот. Особенно нас озадачил тест производительности по вычислению простых чисел.

Поэтому мы решили обратиться к коллегам-программистам и попросить помощи в проверке наших результатов, запустив сами тесты. К нашему удивлению, разные люди сообщали совершенно разные результаты по сравнению с нашими, т. е. если в нашем случае JavaScript был быстрее WASM, то в их случае было наоборот — WASM был (значительно) быстрее JavaScript. После нескольких итераций мы исключили несколько разных факторов, таких как разные настройки Rust/WASM, операционные системы и веб-браузеры. Только когда мы начали изучать спецификацию оборудования, мы поняли, что существует корреляция между архитектурой процессора и разницей в производительности при использовании WASM или JavaScript. Это открытие побудило нас выполнить целый набор новых тестов на различных архитектурах ЦП, которые мы описываем в разделе 4 (часть 2 этой статьи).

3.3 Обработка данных

До сих пор мы сосредоточились на тестах производительности, разработанных в контролируемых условиях с использованием одних и тех же очень простых функций. Хотя эти тесты были отличным упражнением, поскольку они привели нас к обнаружению некоторых интересных зависимостей между технологией и архитектурой процессора, они не полностью отражают реальные приложения. Поэтому мы решили расширить наше исследование дополнительными тестами, ориентированными на обработку данных. И на Rust, и на JavaScript мы разработали простой процессор телеметрии космического корабля, который будет получать двоичный поток кадров телеметрии; затем он будет анализировать эти кадры и извлекать пакеты телеметрии космического корабля. Мы использовали телеметрию одного из космических аппаратов ЕКА — ExoMars Rover и Surface Platform, полученную во время одной из наших кампаний по проверке системы. Процессор телеметрии получает данные в виде бинарного файла (сложенные кадры телеметрии космического корабля в формате CCSDS [11]), загружаемого через веб-браузер, который также запускает обработку.

WASM действительно может показать свою превосходную производительность в этом тесте. Рисунок 8 показывает, что мы можем ожидать значительного повышения производительности в версии WASM. Это имеет смысл, потому что WASM может использовать очень оптимизированные типы данных для разбора данных телеметрии, например, имея возможность назначать 8-битное числовое значение целочисленному типу данных u8 [12]. Поскольку JavaScript не предлагает различные числовые типы данных (по крайней мере, не из коробки), мы должны присвоить одно и то же 8-битное числовое значение 64-битному числовому типу данных. Это также хорошо видно по потреблению памяти. JavaScript требует 180% памяти в Chrome и 225% в Firefox соответственно по сравнению с той же реализацией в Rust. JIT-оптимизатор JavaScript попытается выполнить ту же оптимизацию в отношении типов данных, но он не сможет быть достаточно агрессивным, чтобы конкурировать с версией WASM.

Излишне говорить, что работа с двоичными данными в Rust проще, чем в JavaScript, из-за его низкоуровневого характера, предлагающего более простые способы обработки одно- или многобитных значений.

Продолжить чтение: Сравнение производительности между WASM/Yew и JavaScript/React — Часть 2

Ссылки:
[1] Тренды Google: WebAssembly, [Онлайн; по состоянию на 09 августа 2022 г.]
[2] Опрос разработчиков Stack Overflow 2022: языки программирования, сценариев и разметки, [Онлайн; по состоянию на 09.08.2022]
[3] Участники Википедии, Rust (язык программирования), [Онлайн; по состоянию на 09.08.2022]
[4] Участники Википедии, JavaScript, [Онлайн; по состоянию на 09 августа 2022 г.]
[5] Эрик Гарсия, Языки программирования, одобренные для использования на стороне сервера в Meta, [Онлайн; по состоянию на 09.08.2022]
[6] Джесси Ховарт, Почему Discord переходит с Go на Rust, [Онлайн; по состоянию на 09 августа 2022 г.]
[7] Маркус Кольхейз, Сравнение веб-фреймворка Rust, [Онлайн; по состоянию на 09.08.2022]
[8] Тренды NPM: @angular/core vs angular vs react vs vue, [Online; по состоянию на 09.08.2022]
[9] Участники Википедии, Функция Аккермана, [Онлайн; по состоянию на 15 августа 2022 г.]
[10] Участники Википедии, Гипотеза Коллатца, [Онлайн; по состоянию на 15 августа 2022 г.]
[11] Консультативный комитет по системам космических данных (CCSDS), [Онлайн; по состоянию на 09.08.2022]
[12] Стив Клабник, Кэрол Николс, Типы данных — язык программирования Rust, [Online; по состоянию на 16 августа 2022 г.]