Array SMI vs Double

V8 хранит массивы в специализированных режимах elements: PACKED_SMI, PACKED_DOUBLE, PACKED_ELEMENTS, плюс HOLEY-варианты. Переход между ними — деоптимизация массива.

Иерархия типов elements

PACKED_SMI_ELEMENTS         (только SMI, без дыр)
       ↓
PACKED_DOUBLE_ELEMENTS      (целые + float, без дыр)
       ↓
PACKED_ELEMENTS             (любые объекты, без дыр)
       ↓
HOLEY_*                     (с дырами — медленнее)
       ↓
DICTIONARY_ELEMENTS         (hashmap)

Переход только вниз. Назад дороги нет.

Что вызывает переход

const a = [1, 2, 3];        // PACKED_SMI
a.push(1.5);                // → PACKED_DOUBLE
a.push('x');                // → PACKED_ELEMENTS
a[100] = 1;                 // → HOLEY (дыра)
delete a[5];                // → HOLEY
new Array(1000);            // ← сразу HOLEY!

Парадокс double быстрее SMI

В одном Node-бенчмарке массив double обогнал массив SMI (~0.95 vs ~1.4с). Причина — аллокация через push в Node реаллоцирует buffer хуже для SMI.

В чистом V8 (d8) и Chrome — SMI снова в 2 раза быстрее double.

«В Chrome float-массив в 2 раза МЕДЛЕННЕЕ SMI. В Node float-массив реально быстрее SMI (~0.95 vs ~1.4с).» «Float физически не может обгонять SMI на чистой математике.»

Подводные камни

  • new Array(n) создаёт holey массив сразу — медленный.
  • Лучше Array.from({length: n}, ...) или заранее fill(0).
  • Один null/undefined в массиве чисел → переход в PACKED_ELEMENTS.
  • Удаление через delete arr[i] создаёт дыру. Лучше splice.

Источники

См. также