As For JS
3.43K subscribers
130 photos
12 videos
4 files
376 links
As For JavaScript...
Обсуждения — @AsForJsTalks
Download Telegram
Ответ [Часть 3/4]: #array #perf #v8 #opti #mem #COW
В ответе, который шел выше (Часть 1/4 и часть 2/4), намеренно был не указан еще один важный аспект оптимизаций со стороны V8. Связано это было с желанием не дробить рассказ о типах представления, зависящих от данных в Array.

Особый случай, COW.
Каждый из обозначенных ранее тиров, в том числе и HOLEY - имеют особый подтип - COW.
При создании Exotic Object Array - V8 допускает еще одну форму - COW, которая предполагает, что этот Array не будет никогда изменяться. То есть это некоторая статичная форма Array, данные которой всегда будут теми же что и при иницализации, и сам по себе этот Array никогда не изменится.
COW расшифровывается как: Copy On Write. Скопировать в случае записи.

Например:
var theArr = [1, 2, 3]; // PACKED_SMI_ELEMENTS (COW);
var theArr = [1, 2, , 4]; // HOLEY_SMI_ELEMENTS (COW);
var theArr = ["lorem", 2]; // HOLEY_ELEMENTS (COW);

при этом стоит изменить любой из элементов, или добавить элемент, или удалить элемент, как статус COW будет анулирован.
Например:
var theArr = [1, 2, 3]; // PACKED_SMI_ELEMENTS (COW);
theArr[0] = 2; // PACKED_SMI_ELEMENTS;

var theArr = ["lorem", 2]; // HOLEY_ELEMENTS (COW);
theArr.pop(); // HOLEY_ELEMENTS

но при этом:
var theArr = new Array(1, 2, 3); //PACKED_SMI_ELEMENTS без COW
var theArr = new Array(3); // HOLEY_SMI_ELEMENTS без COW


COW - это особая форма представления Array, которая по сути является самой быстрой. То есть когда я вначале рассказывал о том, что PACKED_SMI_ELEMENTS это наш наиглавнейший ориентир - я намеренно соврал. И сделал это для того, чтобы вначале не сбивать с толку этим особым случаем, когда только что созданный при помощи литеральной формы Array получает особый статус COW.
var theArr = [1, 2, 3]; // PACKED_SMI_ELEMENTS (COW);

Но точно такой же Array созданный при помощи конструктора, подобного статуса не получает.
var theArr = new Array(1, 2, 3); //PACKED_SMI_ELEMENTS
var theArr = []; //PACKED_SMI_ELEMENTS


Промежуточный итог:
Любая литеральная форма связывания Array создает Exeotic Object Array, с типом который отвечает тем данным, которые присутствуют внутри литерала Array. При этом, подобный созданный Array будет иметь статус COW. То есть RunTime будет предполагать что данный Array никогда не будет меняться.
Исключение
создание пустого Array: var theArr = [];

Любая форма создания Array при помощи конструктора Array ( new Array( [...] ) ); будет приводить к созданию обьекта, представление которого будет базироваться на данных из которых создавался обьект, но при этом особого флага COW установлено не будет. То есть RunTime будет предполагать, что подобный Array будет изменяться.


На что влияет флаг COW
:
Подобный обьект занимает наименьший обьем памяти в сравнении с аналогичным. И получит особый род оптимизаций на уровне машинного кода, которые будут предполагать, что этот Array никогда не будет изменен. То есть вплоть до извлечения константных значений из Array и подстановки их в код вместо прямого полноценного обращения обьекту по его ключу.

При этом, любая попытка изменить подобный обьект, приведет к снятию флага COW, и пересозданию оригинального обьекта заново. Что требует дополнительных ресурсов.

Например:
var theArr = [1, 2, 3]; // PACKED_SMI_ELEMENTS (COW);
theArr[0]=2; // Будет
полностью пересоздан в памяти обьект theArr без флага COW.



Выводы про COW
Иными словами, когда мы знаем, что наш Array имеет какие то базовые значения, и мы знаем что этот Array будет изменяться, то правильной формой обьявления его, для случая V8 будет:
new Array ( 1,2,3);

Если же мы уверены в том, что Array который нам требуется, не будет притерпевать никаких изменений, то есть сохранит свою форму и данные ровно такими как при инициализации - то мы должны использховать литеральную форму создания Array
[1, 2, 3];
так как в этом случае мы получим дополнительные оптимизации как по памяти, так и с точки зрения манипуляции обьектом, которые будут исходить из предположения, что этот Array никогда не изменится.
👍71
Ответ [Часть 4/4]: #array #perf #v8 #opti #mem
Сведение воедино всего что сказано в частях с 1 по 3


Создание Array при помощи пустого литерала,
var  theArr = [];  //
<FixedArray[4]> [PACKED_SMI_ELEMENTS]
при этом будет создана только базовая структура обьекта Array, который будет ссылаться на стандартную заглушку.
То есть не будет иметь ни одного подготовленного слота для элементов.
Обратите внимание, что в это случае Array не создается в форме COW.


Создание Array при помощи литерала с одним и более элементов
Создание exotic object Array, при помощи литерала содержащего минимум один элемент, приводит к созданию Array с особым флагом COW. В этом случае RunTime при оптимизации отталкивается от мысли, что подобный Array статичен, и никогда более меняться не будет.
Что логично, если вспомнить насколько часто в JS принято в коде писать что-то наподобие:
new Map( [ ["a", 1], ["b" 2] ] );
То есть V8 в первую очередь нацелен на то, чтобы максимально оптимизировать работу с подобными синтаксическими конструкциями, где литеральная форма массива используется как какой-то промежуточный код.

Любые манипуляции с Array созданным литералом, приводят к копированию всех элементов обьекта Array в новое место в куче уже без флага COW.
var theArr = [1, 2, 3]; //0x340b00267491 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
theArr.push(2); //0x340b0012ba81 <FixedArray[22]> [PACKED_SMI_ELEMENTS]

произошло полное копирование всех элементов изначального Array, плюс выделение дополнительных 19 - слотов предполагая что Array будет рости.

Другой пример:
var theArr = [1, 2, 3]; //0x340b00267491 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
theArr[0]=1; 0x340b0012be69 <FixedArray[3]> [PACKED_SMI_ELEMENTS]
в этом случае, был просто пересоздан Array с копированием всех 3 элементов. Дополнительные слоты не создавались.



Создание Array при помощи конструrтора new Array() или функции Array();
var theArr= new Array(); // <FixedArray[4]> [PACKED_SMI_ELEMENTS] 
var theArr=Array();
// <FixedArray[4]> [PACKED_SMI_ELEMENTS]
при этом, в отличии от Array созданного при помощи пустого литерала, обьект Array будет сразу содержать 4 подготовленых слота для элементов.



А теперь про выделение слотов под элементы, то есть фактически аллокация памяти:
При обращении к элементу выше существующей границы ( Например для Array() это будет индекс 4) происходит выделение дополнительных слотов по формуле:
Длина массива с уже добавленным элементом + 50% от этой длины + 16
Например:
var theArr = [1, 2, 3];
theArr[5]=1;
Будет алоцировано 24 элемента:
Длина массива после добавления элемента: 5
50% от этой длины: 5/2 = 2.5 => 3
+16
Итого:
5 + 3 + 16 = 24 элемента.

Каждый элемент занимает от 4 байт для 32 битных систем и 8 байт для 64 битных.
При этом важно помнить, что при алокации новых слотов, если необходимого обьема памяти непосредственно за предыдущим блоком не будет - то это приведет к полному копированию всех элементов в новую область памяти.

Выводом из этого всего идет очень простой тезис - чем меньше аллокаций тем лучше.
То есть нужно планировать размеры массивов в том случае, если они могут расти.
При этом, чем больше массив, тем дороже будет операция аллокации новых элементов.
👍10