Оптимизации хвостовых вызовов WasmGC и Wasm теперь являются базовыми. Недавно доступны.

Опубликовано: 29 января 2025 г.

Сборка мусора WebAssembly (WasmGC)

Существует два типа языков программирования: языки программирования со сборкой мусора и языки программирования, требующие ручного управления памятью. Примерами первых, среди многих других, являются Kotlin, PHP или Java. Примерами последних являются C, C++ или Rust. Как правило, языки программирования более высокого уровня, скорее всего, будут иметь сбор мусора в качестве стандартной функции.

Проще говоря, сбор мусора — это попытка освободить память, которая была выделена программой, но на которую больше не ссылаются. Такая память называется мусором. Существует множество стратегий реализации сборки мусора. Одним из самых простых для понимания является подсчет ссылок , целью которого является подсчет количества ссылок на объекты в памяти.

Это может показаться началом, но языки программирования реализованы на других языках программирования. Например, среда выполнения PHP в основном реализована на C. Если разработчики хотят скомпилировать такой язык, как PHP, в Wasm, им обычно необходимо скомпилировать все части, такие как синтаксический анализатор языка, поддержку библиотек, сборку мусора и другие важные компоненты.

Wasm работает в браузере в контексте основного языка JavaScript. В Chrome JavaScript и Wasm запускаются в V8, движке JavaScript с открытым исходным кодом от Google. А в V8 уже есть сборщик мусора. Это означает, что разработчики, использующие, например, PHP, скомпилированный в Wasm, в конечном итоге отправляют реализацию сборщика мусора портированного языка (PHP) в браузер, в котором уже есть сборщик мусора, что, как бы расточительно это ни звучало. Здесь на помощь приходит WasmGC.

Чтобы узнать больше о WasmGC, прочитайте статью «Сборка мусора WebAssembly» (WasmGC), которая теперь включена по умолчанию в Chrome , а если вы хотите углубиться в подробности, ознакомьтесь с сообщением в блоге V8 «Новый способ эффективного внедрения языков программирования со сборкой мусора в WebAssembly».

Оптимизация хвостового вызова Wasm

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

int sum(List* list, int acc) {
  if (list == nullptr) return acc;
  return sum(list->next, acc + list->val);
}

При обычном вызове это занимает пространство стека O(n): каждый элемент списка добавляет новый кадр в стек вызовов. При достаточно длинном списке это может очень быстро переполнить стек. Заменяя вызов переходом, оптимизация хвостового вызова эффективно превращает эту рекурсивную функцию в цикл, который использует пространство стека O(1):

int sum(List* list, int acc) {
  while (list != nullptr) {
    acc = acc + list->val;
    list = list->next;
  }
  return acc;
}

Эта оптимизация особенно важна для функциональных языков. Они в значительной степени полагаются на рекурсивные функции, а чистые, такие как Haskell, даже не предоставляют структуры управления циклом. Любая пользовательская итерация обычно так или иначе использует рекурсию. Без оптимизации хвостового вызова это очень быстро привело бы к переполнению стека для любой нетривиальной программы, которая в противном случае быстро исчерпала бы пространство стека.

Изначально WebAssembly не допускал такой оптимизации хвостовых вызовов, но ситуация изменилась с появлением предложения Tail Call Extension . Чтобы углубиться, прочитайте статью хвостовые вызовы WebAssembly в блоге V8.

Выводы

Теперь, когда оптимизация WasmGC и хвостовых вызовов стала базовой версией, больше приложений могут использовать эти функции для повышения производительности, как, например, это сделал Google Sheets, перенеся обработчик вычислений Google Sheets в WasmGC .