Как V8 работает с числами. Small Integer теория
В JavaScript по спецификации все числа (
Что такое Smi
Smi — это способ представления небольших целых чисел без выделения памяти в куче:
- Smi хранятся прямо в указателе
- Могут занимать 31 бит (на 32-битных системах) или 32 бита (на 64-битных системах)
- Диапазон значений для 64 битной системы: от
Формат хранения для разных систем:
Smi всегда заканчивается нулевым битом (
Заглянем в исходники V8
Формат Smi описан в исходниках:
- smi.h
- v8-internal.h
Первое на что можно обратить внимание, задание
Отмечу, что несколькими строками ниже, лежит всеми любимая путаница про "примитивные типы". Однако, как в исходном коде, так и в спецификации, упоминаются
И можно найти константы для работы с тегом плюс маску для проверки:
### Как определяется диапазон значений Smi
Границы Smi заданы так:
Как это соответствует
Для получения значения
Мы видел, что класс Smi статичный
Это означает, что его нельзя создать через
Обратимся к определению struct
И так мы получаем формулу для 64 битной системы:
Сначала выполняется
После этого сдвигаем на
Получаем значением
Подведем итог
Smi:
- не требуют аллокации памяти
- не создают объект HeapNumber
- позволяют JIT-компилятору делать простые арифметические операции без проверок
Это особенно важно внутри циклов или горячих участков кода. Рассмотрим это в практической части в новых постах.
---
#V8 #JavaScript #Smi #Performance #JSразбор
В JavaScript по спецификации все числа (
number) — это 64-битные числа с плавающей запятой (IEEE-754 Double). Но внутри V8 используется множество стратегий, чтобы сделать работу с числами быстрее. Одна из таких — Smi (Small Integer).Что такое Smi
Smi — это способ представления небольших целых чисел без выделения памяти в куче:
- Smi хранятся прямо в указателе
- Могут занимать 31 бит (на 32-битных системах) или 32 бита (на 64-битных системах)
- Диапазон значений для 64 битной системы: от
-2^31 до 2^31 - 1.Формат хранения для разных систем:
// для 32-битной системы
[31 bit signed int] 0
// для 64-битной системы
[32 bit signed int] [31 bit zero padding] 0
Smi всегда заканчивается нулевым битом (
LSB = 0) — это так называемый тег-бит, отличающий Smi от указателей на другие объекты, у которых LSB = 1. Подробнее об этом в теме Tagged Pointer.Заглянем в исходники V8
Формат Smi описан в исходниках:
- smi.h
- v8-internal.h
Первое на что можно обратить внимание, задание
alias для типа Number.using Number = Union<Smi, HeapNumber>;
Отмечу, что несколькими строками ниже, лежит всеми любимая путаница про "примитивные типы". Однако, как в исходном коде, так и в спецификации, упоминаются
primitive value.// A primitive JavaScript value, which excludes JS objects.
using JSPrimitive =
Union<Smi, HeapNumber, BigInt, String, Symbol, Boolean, Null, Undefined>;
И можно найти константы для работы с тегом плюс маску для проверки:
// Tag information for Smi.
const int kSmiTag = 0; // значение тега для Smi (малых целых чисел)
const int kSmiTagSize = 1; // размер тега в битах (1 бит)
const intptr_t kSmiTagMask = (1 << kSmiTagSize) - 1; // маска для выделения тега из указателя (равна 1)
### Как определяется диапазон значений Smi
Границы Smi заданы так:
static const int kSmiMinValue =
(static_cast<unsigned int>(-1)) << (kSmiValueSize — 1);
static const int kSmiMaxValue = -(kSmiMinValue + 1);
Как это соответствует
-2³¹ и 2³¹-1? Разберёмся с этим по порядку.Для получения значения
kSmiValueSize нам снова необходимо обратиться к коду.Мы видел, что класс Smi статичный
class Smi : public AllStatic
Это означает, что его нельзя создать через
new и в классе будут только статичные методы.Обратимся к определению struct
SmiTagging. В коде их две для разных платформ и в них же записан enum с необходимыми константами. kSmiShiftSize - на сколько бит сдвигается значение и kSmiValueSize количество битов, выделенных под значением. В структуру же передается размер указателя в байтах 4 (32 бита) или 8 (64 бита).struct SmiTagging<4> {
enum { kSmiShiftSize = 0, kSmiValueSize = 31 };
...
}
struct SmiTagging<8> {
enum { kSmiShiftSize = 31, kSmiValueSize = 32 };
...
}
И так мы получаем формулу для 64 битной системы:
(static_cast<unsigned int>(-1)) << (32 - 1)
Сначала выполняется
cast значения -1 к беззнаковому виду и становится 0xFFFFFFFF (все 32 бита равны 1). Для понимания можно ознакомиться с представлением отрицательных целых чисел в беззнаковом виде (Two's complement).После этого сдвигаем на
31 бит:0xFFFFFFFF << 31
10000000 00000000 00000000 00000000
0x80000000 = 2147483648
Получаем значением
int (знаковое), то минимальная граница smi чисел равна -2147483648. Убираем знак и добавляем 1 и получаем верхнюю границу 2147483647Подведем итог
Smi:
- не требуют аллокации памяти
- не создают объект HeapNumber
- позволяют JIT-компилятору делать простые арифметические операции без проверок
Это особенно важно внутри циклов или горячих участков кода. Рассмотрим это в практической части в новых постах.
---
#V8 #JavaScript #Smi #Performance #JSразбор
🔥13🤩2