Тот самый канал по JavaScript.
Личный блог автора - @just_genych
По вопросам рекламы или разработки: @g_abashkin
Post #3583
415
CPU’s hidden bottleneck: cache misses, false sharing и Node.js event loop
Когда high-load сервис на Node.js начинает тормозить, обычно думают на I/O, GC или асинхронщину. Я часто видел другое: настоящий убийца производительности — CPU cache misses и false sharing. В production на Node.js, особенно в воркерах с SharedArrayBuffer, это критично для финансовых транзакций, игровых серверов или обработки медиа.
Что такое false sharing в контексте Node.js
Допустим, у вас массив флагов для тысячи воркеров в Worker Threads. Вы обновляете их из разных потоков.
Когда flags[1] меняется, процессор инвалидирует кеш-линию на ядре Воркера 1. Тому приходится перечитывать данные из RAM. Это false sharing — задержка сотни циклов вместо единиц. Event loop ждет завершения воркеров, которые тратят 30% времени на перезагрузку кеш-линий, что блокирует цикл дольше. CPU загружен, а полезная работа не растет.
Методы предотвращения: padding и атомарность
Разделите данные padding, чтобы критические переменные попали в разные кеш-линии:
Используйте Atomics с SharedArrayBuffer правильно — размещайте горячие переменные с отступами. Минимизируйте запись: вместо флагов применяйте очереди с атомарным каунтером для чтения.
Типичная ошибка и профилирование
Ошибка: надеяться, что Node.js сам управляет памятью потоков. На практике false sharing встречается в высоконагруженных воркерах. Профилируйте cache misses на Linux:
Вывод:
Понимание кеш-иерархии CPU и false sharing — must для сеньоров, работающих с high-load на Node.js, чтобы не допускать пробуксовки event loop из-за физики процессора.
Когда high-load сервис на Node.js начинает тормозить, обычно думают на I/O, GC или асинхронщину. Я часто видел другое: настоящий убийца производительности — CPU cache misses и false sharing. В production на Node.js, особенно в воркерах с SharedArrayBuffer, это критично для финансовых транзакций, игровых серверов или обработки медиа.
Что такое false sharing в контексте Node.js
Допустим, у вас массив флагов для тысячи воркеров в Worker Threads. Вы обновляете их из разных потоков.
// Плохо: флаги лежат рядом в памяти
const flags = new Int32Array(1024 * 1024);
// Воркер 1 пишет в flags[0], Воркер 2 — в flags[1]
// Оба элемента — в одной кеш-линии (64 байта)
Когда flags[1] меняется, процессор инвалидирует кеш-линию на ядре Воркера 1. Тому приходится перечитывать данные из RAM. Это false sharing — задержка сотни циклов вместо единиц. Event loop ждет завершения воркеров, которые тратят 30% времени на перезагрузку кеш-линий, что блокирует цикл дольше. CPU загружен, а полезная работа не растет.
Методы предотвращения: padding и атомарность
Разделите данные padding, чтобы критические переменные попали в разные кеш-линии:
const CACHE_LINE_SIZE = 64; // байт
const PADDING = CACHE_LINE_SIZE / 4; // для Int32 (4 байта)
const flags = new Int32Array(1024 * 1024 * PADDING);
const idx = (i) => i * PADDING;
// Доступ: flags[idx(0)], flags[idx(1)]
Используйте Atomics с SharedArrayBuffer правильно — размещайте горячие переменные с отступами. Минимизируйте запись: вместо флагов применяйте очереди с атомарным каунтером для чтения.
Типичная ошибка и профилирование
Ошибка: надеяться, что Node.js сам управляет памятью потоков. На практике false sharing встречается в высоконагруженных воркерах. Профилируйте cache misses на Linux:
perf stat -e cache-misses node app.js. Если miss ratio >5% — проблема в кеш-иерархии.Вывод:
Понимание кеш-иерархии CPU и false sharing — must для сеньоров, работающих с high-load на Node.js, чтобы не допускать пробуксовки event loop из-за физики процессора.











