До вмісту
fr0staman
Усі статті

Чого коштує перетин межі WASM

3 хв читання

У хості плагінів на WebAssembly Component Model кожен аргумент і кожне повернуте значення перетинають межу між двома просторами пам'яті. Здебільшого на це можна не зважати. З тілами запитів — не можна, і цифри достатньо різкі, щоб змінити дизайн.

Тіла як буфери

Перша версія оголошувала HTTP-тіла найочевиднішим способом:

record http-request {
    method: string,
    uri: string,
    headers: list<http-header>,
    body: option<list<u8>>,
}

list<u8> — це буфер. Тож завантаження приходить у хост, буферизується там, копіюється через межу й матеріалізується ще раз у лінійній пам'яті гостя — три копії тих самих байтів ще до того, як плагін на них глянув. Гірше: цю ціну платить кожен плагін, зокрема ті, що тіла взагалі не торкаються.

Перехід в обидва боки на stream<u8> змінив дві речі:

Тіло, яке плагін ігнорує, стало майже безкоштовним. Запит на 8 МіБ пройшов шлях від 2638 мкс до 8 мкс. Нічого не копіюється, бо нічого не читається.

Тіло, яке плагін читає, стало повільнішим. Приблизно в 4–5 разів за граничною вартістю на МіБ: 543 мкс проти 112 мкс у старого масового копіювання. Невеликі JSON-запити платять десятки мікросекунд, яких раніше не платили.

Це справжній розмін, а не безкоштовна перемога. Просто тут він правильний: більшість маршрутів плагінів — це перевірки статусу й короткі команди, а поодинокі великі завантаження змушували все інше платити за сам факт свого існування. Якби навантаження складалося переважно з великих тіл, які всі читаються, виграла б буферна версія.

Стримінг змінює те, чим може бути помилка

Щойно хост повертає статус і заголовки одразу, як їх видав гість, — а саме в цьому сенс стримінгу, — збій посеред тіла стається вже після того, як рядок статусу пішов у мережу. Він не може стати 500-ю. Гість може впасти, паливо може закінчитися посеред тіла, а клієнт усе одно побачить 200 і обрізану відповідь.

Тож дизайн мусить сказати, що це означає. Тут call_http зберігає бюджет у десять мільйонів одиниць палива, велике тіло тепер може його вичерпати, а падіння посеред тіла не може змінити вже надісланий статус. Нічого екзотичного — це те саме обмеження, що й у будь-якого стримінгового сервера, — але перехід від буферів до стрімів тихо перетворює категорію «повернути помилку» на категорію «клієнт отримав пів відповіді».

У пісочниці немає годинника

Другий урок не мав стосунку до продуктивності. Плагіни не могли спати. Не «спати було повільно» — не було способу це виразити. Гості збираються під wasm32-wasip2, а хост не надавав таймера, тож усе, пов'язане з часом, було просто недоступним.

Додати це не означало підключити крейт. WASI має лінкувати хост, а розміщення його в одному світі з інтерфейсом плагіна змусило bindgen хоста вимагати реалізацію Host і зіткнутися з рантаймом p3. Розв'язанням стало розділення світів: світ plugin-guest, який додає wasi:clocks@0.3 поверх інтерфейсу плагіна, з визначенням годинника, скопійованим із тієї версії wasmtime-wasi, яку хост справді лінкує, — щоб обидві сторони збігалися.

Загальна думка: пісочницю визначає те, що хост погодився передати. Легко думати про неї як про «звичайний код, тільки ізольований». Насправді це радше навпаки: нічого не існує, доки не надано, — включно з можливостями настільки буденними, що ви ніколи не сприймали їх як можливості. Мене здивував час. Це навряд чи останній сюрприз.

Чи вартий був Component Model

Для системи плагінів — так. Плагіни компілюються один раз у .wasm, завантажуються в хост, з яким їх не збирали, оголошують свої маршрути в специфікації, яку хост об'єднує, і працюють під лімітами палива без доступу до чогось, що не було явно злінковане. Отримати це від динамічної бібліотеки означало б їй довіряти.

Ціна в тому, що кожне рішення про інтерфейс стає рішенням про представлення даних, ухваленим у WIT-файлі, з наслідками для продуктивності, які видно лише під навантаженням. За ізоляцію це чесна ціна. Але не безкоштовна.

Поділитися