Как на самом деле работают типы данных в js?

В память врезалась статья которую я когда-то читал, но совсем забыл где и когда, и сейчас я просто не могу её найти, но я запомнил суть этой статьи, но я не уверен что запомнил и понял все верно.

Так как в js неявная типизация, то интерпретатор сам выбирает какой тип данных движок присвоит переменной. Например
/**
*
* Обычное число, дается тип int и выделяется 4 байта
*/
let a = 1;


Но, когда мы работаем с дробными числами, то нужно выделять 8 байт памяти. И, если мы преобразуем int в float на ходу, то процессор не может просто добавить ещё 4 ячейки памяти рядом с уже имеющимися. Ему нужно вытащить значение из 4-ех ячеек памяти, и переместить в новые 8 ячеек. Получается что:
// Первый вариант
let a = 1;
a += 0.5;

// Второй вариант
let a = 1.0;
a += 0.5;


Второй вариант более оптимальный, так как мы даем понять интерпретатору что нужно сразу выделить больше ячеек памяти, так как число с плавающей точкой. Насколько это правда? В чем я ошибаюсь?
Если это и так, маловероятно что это оказывает хоть сколько значимый результат на производительность кода. Это больше академический интерес, хочу разобраться как оно работает.

Схема в доке v8 говорит что у int всего 2 типа, int32 и unit32, а int8, int16, float32 etc. есть только у массивов. Получается, что все работает не так как я описал?
  • Вопрос задан
  • 892 просмотра
Решения вопроса 1
@Free_ze
Пишу комментарии в комментарии, а не в ответы
Схема в доке v8 говорит что у int всего 2 типа, int32 и unit32, а int8, int16, float32 etc. есть только у массивов.

Логично, что int не может быть float (=

В статье про оптимизацию говорилось верно, что Number до некоторого размера может храниться более оптимально. Кусочек кода, который дёргает парсер, когда встречает литерал:

Handle<Object> Factory::NewNumber(double value, AllocationType allocation) {
  // Materialize as a SMI if possible.
  int32_t int_value;
  if (DoubleToSmiInteger(value, &int_value)) {
    return handle(Smi::FromInt(int_value), isolate());
  }
  return NewHeapNumber(value, allocation);
}



Второй вариант более оптимальный, так как мы даем понять интерпретатору что нужно сразу выделить больше ячеек памяти, так как число с плавающей точкой. Насколько это правда?

Неправда потому, что значение иммутабельно в данном случае и будет выделение третьего куска памяти под результат. И одно лишь наличие точки не делает число не целым (что вполне объяснимо, ибо JS-юзер разницы не видит by design).

По коду сложения чисел видно, что для описаных вами случаев оба объекта приводятся к HeapNumber (aka "большой Number", но не путать c BigInt) и складываются, а ссылка начинает указывать на новый объект (даже в случае укороченной записи, ибо a += b <=> a = a + b).

Задействованная ветка:
TF_BUILTIN(Add, AddStubAssembler) { // Операция сложения
  ....
  BIND(&if_left_smi);               // Левый операнд - "маленький" int
  {
    .....
    BIND(&if_right_heapobject);     // Правый операнд - HeapNumber
    {
      ....
      var_left_double.Bind(SmiToFloat64(left));
      var_right_double.Bind(LoadHeapNumberValue(right));
      Goto(&do_double_add);
      .....
    }  // if_right_heapobject
  }    // if_left_smi

  ....

  BIND(&do_double_add);
  {
    Node* value = Float64Add(var_left_double.value(), var_right_double.value());
    Return(AllocateHeapNumberWithValue(value));
  }
}
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
Robur
@Robur
Знаю больше чем это необходимо
Это больше академический интерес, хочу разобраться как оно работает.

Это вы сейчас в очень глубокую нору заглядываете.

Движки стараются оптимизировать по максимуму, поэтому там очень много всего накручено помимо наличия int32.
Я не удивлюсь если в вашем конкретно примере они сделают просто a=1.5 в обоих случаях и код будет идентичный до байта.
а потом эту a подставят куда надо и оптимизируют там еще что-то, например b=c+a превратится в b=c+1.5

кроме того, есть разные компиляторы - которые генерируют оптимальный код для частных случаев, или более "общий" но более медленный.

Например для каждой переменной во время работы записывается какой тип в нее приходит, если этот участок кода вызывается достаточно часто - он помечается "горячим" и затем смотрится, если у вас в "a" всегда один и тот же тип, то генерируется быстрый код для этого типа, и ставятся проверки, если вдруг придет что-то другое, то этот код выкидывается и происходит возврат к более общему.

И много чего другого, лишь бы быстрее работало.
Даже если вы прямо сейчас разберетесь во всем, то завтра запилят новую оптимизацию где-то в глубине движка и что-то будет работать уже по другому.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы