As For JS
3.44K subscribers
129 photos
12 videos
4 files
371 links
As For JavaScript...
Обсуждения — @AsForJsTalks
Download Telegram
Трансляция началась…

#онлайн_встреча
@AsForJavaScript
🔥2🐳1
Live stream finished (3 hours)
Media is too big
VIEW IN TELEGRAM
Запись трансляции от 25.04.2023

#онлайн_встреча
@AsForJavaScript
👍9🐳1
Вопрос о алокации JS массивов: #array #perf #v8 #opti
Насколько я помню в си мы аллоцирум палять для массива и каждый раз когда мы хотим увеличить число элементов в массиве, мы должны сделать вновь аллоцировать память. Имеет ли смысл делать тоже самое на js? var arr = [1, 2, 3];, я хочу добавить ещё два элемента в массив, arr.length= 5; arr[3] = 4; arr[4] = 5; и будет ли это влиять на производительность? И что делать если всётаки нудно увеличить длину массива.

Я расширю ответ на все множество ситуаций для подобной темы.

Ответ [Часть 1/4]:
В JavaScript, в случае вопросов о том, что работает быстрее всегда нужно помнить две вещи:
1. Как это закреплено в спецификации
2. Как это оптимизирует конкретный #RunTime

* Если явно не указано другого, то рекомендации идут для RunTime: #V8.

Справка:
V8 - это RunTime от Google. Который встраивается сейчас в Google Chrome и NodeJs. Так же является одним из самых популярных RunTime для встраивания в разного рода штуки для Умного Дома - типа чайников, холодильников и т.д.

Я не знаю на момент написания ответа, иного RunTime, который бы по совокупности факторов, мог бы конкурировать с V8.
Под совокупностью факторов подразумевается: Базовая производительность, производительность в случае оптимизации горячего кода. Простота интеграции RunTime в другие HOST системы.



Как это закреплено в спецификации
Именно для Exotic Object Array, то есть того обьекта который мы создаем при помощи конструктора #Array или литеральной формы [] никакого особого поведения, для оптимизации работы, с точки зрения спецификации - не предусмотрено.
Потому каждый RunTime крутится как хочется.


В случае V8, для exotic object Array используется следующая стратегия:
Внутри RunTime существует 20 вариантов представления подобного обьекта.
Производительность которых сильно отличается друг от друга.

V8 устроен таким образом, что при создании Exotic Object Array, он получает наиболее производительную форму, которая в последствии деградирует до менее производительной, опираясь на те данные которые используются в Array.

Самое быстрое представление:
var theArr = [1, 3, 2];
или
var theArr = new Array (1, 3, 2);

Такое представление называется PACKED_SMI_ELEMENTS.
[1] PACKED: в обьекте (нет и не было) дырок

[2] SMI: элементы состоят из целых чисел со знаком в пределе 2^(32-1). То есть в пределе между -1073741824 до 1073741823. 2^31 степени: 0-2147483647 однако V8 по умолчанию оперирует SMI со знаком, в следсndии чего еще один бит уходит на знак.

[3] ELEMENTS: Значения связаны с числовыми ключами. (0, 1, 2, .... )



Любое превышение заявленого предела для SMI, приводит к преобразованию Exotic Object Array к другой форме, которая называется PACKED_DOUBLE_ELEMENTS.
Например использование значений: 1073741824, NaN, -0, 1.1 etc...
То есть форме, которая хранит в себе 64-bit IEEE floating point number
4👍1
Ответ [Часть 2/4]: #array #perf #v8 #opti
Все прочие представления данных, представляют из себя
PACKED_ELEMENTS.
Например:
var theArr = [ "dsfsd" ]; //PACKED_ELEMENTS
var theArr = [ ()=>{} ]; //PACKED_ELEMENTS
var theArr = [ console, console.log, "fsdf" ]; //PACKED_ELEMENTS
var theArr = [ 1, "Yo", console.log]; //
PACKED_DOUBLE



То есть мы имеет формулу:
PACKED_SMI => PACKED_DOUBLE => PACKED_ELEMENTS

При этом на каждом этапе этой формулы, у нас есть промежуточная форма связаная с дырками HOLEY;
PACKED_SMI => PACKED_DOUBLE => PACKED_DOUBLE
HOLEY_SMI => HOLEY_DOUBLE => HOLET_ELEMENTS


Выделение отдельной строкой для HOLEY (дырок) связано с несколькими обстоятельствами:
HOLEY_SMI медленнее PACKED_SMI, но могут отказаться как быстрее PACKED_DOUBLE так и медленее.
Медленее HOLEY элементы могут оказаться потому, что в случае дырок - произойдет подключение кода, который будет проверять всю цепочку прототипов обьекта:
var theArr = [ 0, 1, , 3];
theArr[2]; // Доступ к HOLEY что приведет проходу по всей цпочке прототипов в поиске значения для ELEMENT с индексом 2.

то есть поскольку цепочка прототипов может быть какой угодно, то дать точно предсказание относительно быстродействия HOLEY представлений достаточной сложно.
Обычно привыкли считать, что в случае цепочки прототипов по умолчанию то:
PACKED_SMI => HOLEY_SMI => PACKED_DOUBLE => HOLEY_DOUBLE
но эта информация сильно устарела, потому как оптимизации для PACKED представлений сделано очень много.

То есть HOLEY это своего рода лотерея.


ИТОГО БАЗОВЫЕ ПРАВИЛА:
1)
V8 изначально думает про наш Array как про самую быструю форму PACKED_SMI_ELEMENTS, и по мере обработки данных в нем, изменяет форму на более медленную. При этом, вернуть форму к более быстрой - уже не будет возможно без полного пересоздания Array. Опуститься вниз можно, подняться вверх нельзя.

2) Избегаем Array, где есть HOLEY elements. Значения, которые не имеют определения. В противном случае, при доступе к подобному элементу, мы провоцируем RunTime осуществлять поиск значения по всей цепочке прототипов. Что сильно усложняет код работы с обьектом.

3) Помним что наиболее быстрым представлением является Array с числами в диапазоне -1073741824 до 1073741823,

4) Все прочие числа: 1.0, -0, +0, NaN, 1073741824 и так далее приводят к созданию более медленного представления PACKED_DOUBLE_ELEMENTS или в случае наличия дырок еще более медленное HOLEY_DOUBLE_ELEMENTS

5) Появление в Array хотябы одного элемента любого другого типа (String Object Function etc...) приводит к созданию самой медленной формы представления: PACKED_ELEMENTS или в случае наличия дырок еще более медленной: HOLEY_ELEMENTS


==================================================================
Примеры создания Exotic Object Array с внутренним типами в V8:
var theArr = [1, 2, 3]; // PACKED_SMI_ELEMENTS
var theArr = new Array (1,2,3); //[1, 2, 3] PACKED_SMI_ELEMENTS
var theArr = new Array(3); // [, , ,] HOLEY_SMI_ELEMENTS

var theArr = [1, 2, 3]; // PACKED_SMI_ELEMENTS
theArr.pop(); // PACKED_SMI_ELEMENTS
theArr.push(5); // PACKED_SMI_ELEMENTS

theArr.push(1.1); // PACKED_DOUBLE_ELEMENTS
theArr.pop(); // PACKED_DOUBLE_ELEMENTS
theArr.push(1); // PACKED_DOUBLE_ELEMENTS
theArr.push(4); // PACKED_DOUBLE_ELEMENTS

theArr.push("lorem ipsum"); // PACKED_ELEMENTS


var theArr = [0, 1, 2]; // PACKED_SMI_ELEMENTS
theArr[4] = 4; //HOLEY_SMI_ELEMENTS
theArr.pop(); /HOLEY_SMI_ELEMENTS 
theArr.pop(); /HOLEY_SMI_ELEMENTS

var theArr = [0, , 2]; // HOLEY_SMI_ELEMENTS
theArr[1]=1; ; // HOLEY_SMI_ELEMENTS
theArr[1]=1.1; // HOLEY_DOUBLE_ELEMENTS

var theArr = [NaN]; // PACKED_DOUBLE_ELEMENTS
10👍1
Ответ [Часть 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
Обобщенный набор рекомендаций по работе с Exotic Object Array
#array #opti #perf #tips

1]
Если Ваш Array имеет фиксированный размер, и при этом его элементы не будут изменяться - смело используйте литеральную форму определения такого обьекта. Например:
var theArr = [1, 2, 3];

2] Если элементы будущего Array будут изменяться, то рекомендуется использовать либо констурктор Array либо его функциональную форму:
var theArr = new Array (1, 2, 3);
theArr[0]=7;
таким образом вы избежите ненужной нагрузки на GC, поскольку Ваш Array сразу будет создан из предположения что элементы будет изменяться. Или способ из бонус 2 в конце поста.

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

4] Настоятельно рекомендуется не делать дырок в Array
var theArr = [1, 2, 3];
theArr[7]=1;
приведет к созданию дырок для элементов с индексами 3,4,5,6. Обработка подобных Array в среднем на 30% медленее чем аналогичных и без дырок.

5] Настоятельно рекомендуется в Array держать элементы одного и того же типа:
1. Целые числа в пределе от -1073741824 до 1073741823
2. Любые прочие числа
3. Все прочие типы данных

6] Не забываем что V8 устроен таким образом, что изменив модель представления Array на худшую, к лучшей уже вернуться нельзя. Только пересоздавать Array.

7] Помним что Array по задумке V8 инженеров, предназначен для обработки данных в условиях, когда для одних и тех же данных многократно повторяются одни и теже операции. Для подобных ситуаций внутри V8 и созданы все оптимизации, которые опираются на описание выше.

8] Памятке по аллокации слотов:
var theArr = []; // 0 слотов.

var theArr = Array(); // 4 слота

var theArr = new Array(); // 4 слота

var theArr = Array(10); // Аллокация 10 слотов. Тип HOLEY_SMI_ELEMENTS

var theArr = Array(1,2,3,4,5); // Аллокация 5 слотов под пять элемента БЕЗ COW

var theArr = [1,2,3, 4, 5]; // Аллокация 5 слотов под пять элемента и статус COW
theArr[0]=1; // Копирование 5 элементов в новую область и снятие статуса COW

theArr.push(1); // 6 + 3 + 16 = аллокация 25 слотов, высокая вероятность копирования всех элементов в новую память. Тип PACKED_SMI_ELEMENTS неизменен.



От себя добавлю.
Особенно сильно морочить себе голову для случаев COW не COW нужно только тогда, когда это оказывается внутри кода с сотнями тысяч повторений.
При этом крайне внимательно относится к аллокации новых элементов в массиве. Эта проблема тем больше, чем больше используемый массив.

Бонус:
V8 имеет вторую форму представления Array которая является полным подобием Map и включает ее тогда, когда колличество дырок в массиве превосходит все границы. Например:
var theArr = [1,2,3];
theArr[1073741823]=1;

И бонус 2:
Определение данных путем использования JSON.parse является намного более производиетльной формой чем формирование данных при помощи литеральных форм как обьекта так и Array.
Что имеет логичное обоснование и явлется рекомендацией от разработчиков V8: https://v8.dev/blog/cost-of-javascript-2019#json
На данных обьемом около 10 килобайт,выгода от подобной инициализации видна невооруженным взглядом и составляет около 20 - 30%.
var theArr = JSON.parse( `[1, 2, 3, ..... 10 0000]`);
var theObj = JSON.parse( `{"name" : "murych", "age": 46}`);

При этом в случае Array, только что созданный обьект всегда будет без статуса COW
А в случае Object, все ключи обязательно попадут в самую быструю форму представления обьекта. In Object Property

Но это уже другая история....


UPD1: поправил пример с JSON.parse. Там при копировании почему то пропали кавычки.
👍10🔥31🐳1
👍8🔥4👨‍💻1
Чи подобається обгортка
Нравится ли обложка
Anonymous Poll
76%
Так (Да)
2%
Ні (Нет)
23%
Я упырь
As For JS
https://www.youtube.com/live/fQ7_GT8_zeM?feature=share
Более короткая версия видео о работе this в языке JavaScript.

Но настоятельно рекомендуем, перед этим, просмотреть предыдущее, более подробное видео о работе this в JavaScript.
3
Я все время стараюсь отсматривать новые видео на YouTube связанные с JS тематикой.
И вот сегодня, видимо совпадение, а может быть это алгоритмы youtube так работают, мне подсунули видео, какой-то it школы, где идет лекция о том, что такое this.
То, что там this описан совершенно не верно - и так понятно. Но я услышал нечто новое - со слов лектора, обьект заданный в литеральной форме не имеет своего собственного this.

Вот таких откровений я вообще не ожидал.

А люди в этой школе платят деньги за обучение.

https://www.youtube.com/watch?v=G18mcIoEL0s
🤡4👌2
До JS сообщества наконец начинает доходить то, что я рассказываю уже лет 7.

Одни переиздают книжки, скромно исправляя глупости написаные рание.
Другие выпускают вот такие статьи.
А третьи уже сделали платные курсы с секретными знаниями о том как работает JS.

Где, ой какая новость, нет переменных.
Где, ой как неудобно вышло, идентификаторы — єто только метки(ссылки).

И т.д. Это статья из очень популярной ленты новостей о JS.

https://www.joshwcomeau.com/javascript/the-const-deception/
🔥5👍2🐳1
Группа для обсуждений изменена на
https://t.me/AsForJsTalks

Cтарая группа останется как архив.
Один из подписчиков прислал вот такое лого.
🔥9🌚2👀2🤡1👨‍💻1
As For JS pinned «Группа для обсуждений изменена на https://t.me/AsForJsTalks Cтарая группа останется как архив.»