lyng/proposals/wasm_vs_lyng_memory.md

6.8 KiB

WASM использует стековую модель памяти. Lyng стеки не использует. В чем разница?

Стек это очень древняя структура, была придумана в 1955 (магазинная память Бауэра и Самельсона), и приняла современную форму в 1960, благодаря Барбаре Лисокв, вполседствии лауреата премии Тьюринга, которого, в свою очередь, за выдающийся вклад в развитие IT кастрировали, формально, за гомосексуализм, но в британии, где на трех джентльменов приходилось тогда, и приходится и теперь, четыре гомосексуала, все понимают, что это был просто предлог.

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

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

Далее, васм использует фиксированную память выделенную на процесс. Ее тоже приходится брать "с запасом", так как если по ходу не хватит, процесс вылетит. И она тоже в изрядной части пустая. Ну и назасладочку, васм был задуман однозадачным, и все попытки туда хотя бы треды засунуть, получаются очень кривыми.

Линг использует фреймы и сопрограммы. Линг вообще треды не использует. Основная команда исполнения программы на линге - сопрограмма (coroutine), ей не нужен стейк, она им не пользуется. Вместо этого каждый вызов в линге, грубо говоря, это вызов сопрограммы. Для него формируется фрейм (не на стеке, а в динамической памяти!), которого гарантировано достаточно для исполнения собственно кода. Он заменяет собой "стековый фрейм", но его размер известен заранее, и его можно распределять и освобождать проще, как обычную динамическую память.

Далее, линг использует ту же динамическую память, что и его родительская платформа. Если он работает на Java, то использует память со сборкой мусора JVM, на Javascript - память машины JS, в нативных машинах - специальнй менеджер памяти со сборкой мусора, от Kotlin Native (довольно хороший и очень быстро развивающийся). В результате, запустить одновременно десять, или сто тысяч программ на лигне, которые будут исполняться конкурентно, вполне реально. Просто попытка запустить 10к васм-машин на v8 или другом движке скорее всего убьет систему, да и 10 тысяч тредов редко какой сервер приложению даст. Огромный перерасход ресурсов и тормоза.

Далее. Васм СТЕКОВАЯ машина со стековыми командами, он без стека вообще не может. Посмотрим как он считает примитивное выражение:

(module
  ;; Экспортируем функцию, чтобы её можно было вызвать из JS
  (func (export "calc") (param $a i32) (param $b i32) (param $c i32) (param $d i32) (result i32)
    ;; --- вычисление (a + b) / c * d ---
    local.get $a      ;; помещаем a на стек
    local.get $b      ;; помещаем b на стек
    i32.add           ;; складываем: стек содержит (a+b)

    local.get $c      ;; помещаем c
    i32.div_s         ;; знаковое деление: стек содержит ((a+b) / c)

    local.get $d      ;; помещаем d
    i32.mul           ;; умножение: стек содержит (((a+b)/c) * d)

    ;; результат остаётся на стеке — это возвращаемое значение функции
  )
)

На ВМ Линга нет стека, он трехадресный универсальный ассемблер, если можно так сказать:

    add_int s1, s2 -> s5 // a + b -> s5
    div_int s5, s3 -> s5 // s5 -> s5 / c
    mul_int s5, s4 -> s5 // s5 -> s5 * d
    return s5

Этот код крайне эффективно реализуется на любом реальном процессоре, и хорошо оптимизируется. Исполняется он без всякого стека.