SMI — Small Integer в V8
Способ V8 хранить маленькие целые числа внутри указателя без аллокации объекта. Через pointer tagging: младший бит указателя помечает, число это или ссылка.
Механика
- Указатель в V8 = слово CPU. Младший бит используется как tag.
bit = 1→ перед нами данные (SMI, целое число прямо в указателе).bit = 0→ перед нами ссылка на объект в куче.- Это позволяет не аллоцировать объект Number для каждой циферки.
«Чтобы не выделять новый числовой объект каждый раз, V8 с помощью техники тегирования указателей позволяет хранить маленькие числа.»
Диапазон SMI зависит от архитектуры
| Архитектура | SMI диапазон |
|---|---|
| 32-bit V8 | 31 бит знакового целого |
| 64-bit V8 (без pointer compression) | 32 бита |
| 64-bit с pointer compression | снова 31 бит |
Цитата: «SMI в зависимости от обстоятельств будет совершенно разным. Если архитектура 32-битная, то SMI это 31 бит, выделяемый на знаковое целое.»
Pointer Compression
V8 сжимает указатели на 64-битке до 32 бит (база + смещение). Экономия памяти 2x, но и SMI снова на 31 бит.
Подводные камни
- Число вышло за SMI → V8 аллоцирует HeapNumber (double в куче) → касание GC и потеря скорости.
Array.smi(массив маленьких целых) ≠Array.double(с плавающей точкой) — разные внутренние storage.arr[0] = 1.5после ряда целых → массив переходит в double-режим (deoptimization).- В одном бенчмарке (Node) массив double-ов оказался быстрее массива SMI из-за особенностей аллокации через
push— но в чистом V8 и Chrome SMI быстрее в 2 раза.
Источники
- Разбор вопроса о Array Double vs Array SMI · AsForJS · 2023-06-23
- Бенчмарк: В Chrome float-массив в 2 раза медленнее SMI; в Node — наоборот (~0.95 vs ~1.4с) из-за реаллокации
- Почему в V8 SMI 31 бит · AsForJS · 2023-06-28
- Реализация хранения данных. Стек и куча. Oddball · AsForJS · 2024-07-27