Фантастическая скорость работы slice связана с оптимизацией Copy-on-Write. Эта оптимизация, при которой копирование больших данных откладывается до тех пор, пока одна из копий не будет изменена. То есть, по сути, создается не копия массива, а новая ссылка на память, в которой хранится ссылка на исходный массив. И лишь в момент изменения нового массива происходит фактическое копирование. Чтобы в этом убедиться, можно в цикле со slice после копирования производить операцию, изменяющую новый массив (например, push). Тогда вся магия исчезает и slice начинает работать не быстрее map или for.
а можно пожалуйста пример кода ? изменяя массив после копирования (10000 элементов) у меня slice (2s) а map(2.7s) chatGPT объясняет что в slice не передается функция и оно не вызывается для каждого элемента по этому он быстрее
Алексей в первом методе создания копии использовал push, что для данной задачи может быть оптимизированно, если написать это немного иначе, то результат будет получше: for (let i = 0; i < 100000; i++) { const m = new Array(array.length) for (let k = 0; k < array.length; k++) m[k] = array[k] } Просто push() как будто добавляет в оригинальный массив элементы изменяя его длину, но на самом деле, это не совсем правда, при работе этого метода JS периодически создаёт новые массивы с удвоенной длиной когда длины нынешнего массива не хватает (JS изначально создаёт массивы с запасом длины). При этом приходится копировать элементы со старого массива в новый и запускать garbage collection что затратно. Если изначально создать массив нужной длины то время сократится вдвое. Но конечно slice() тут всё равно вне конкуренции. И видео всё равно интересное и познавательное.
Спасибо за уместное замечание. Хотя тут есть ещё вторая сторона медали. Мы конечно экономим на реалокациях массива в памяти. Но вот массивы созданные через конструктор Array (с указанием длины) определяются рантаймом V8 как разреженные. И при дальнейшей работе с этими массивами возможна ощутимая просадка производительности. Поэтому поверхносное клонирование массива лучше таки делать через slice или даже через spread (как ни странно).
Чтобы спред опреатор в браузере работал быстрее в таких условиях, его можно использовать в таком виде - Array(arr.length).fill(...arr). Да, скорость все еще будет ниже чем у slice, но не в десятки тысяч раз, а, всего лишь, в сотни. А при однократном копировании одного огромного массива на 50 000 000 элементов разница и вовсе отсутствует, только в этом случае использовать нужно [...arr], иначе произойдет переполнение стека.
Самое интересное, что результаты для браузера и для Node js отличаются) //Node Js 3408 loop for 3339 loop while 1 spread 1 slice 1998 map 3331 filter 2 Array.from 25202 JSON.parse(JSON.stringify) 3 concat 1474 copy by index to new Array() /// 24624 structuredClone 184778 Object.assign // Browser 2485 'loop for' 2455 'loop while' 21746 'spread' 3 'slice' 3870 'map' 2864 'filter' 16841 'Array.from' 16796 'JSON.parse(JSON.stringify)' 2 'concat' 622 'copy by index to new Array()' /// 15350 'structuredClone' 140473 'Object.assign'
Для чистоты эксперимента нужно движок JS было указать и его версию, т.к. в других движках могут присутствовать свои оптимизации и результат может кардинально отличаться. Например, в V8 есть оптимизации для цикла for и while для большого количества итераций. В том же V8 итерационные методы массивов базируется на reducer-е, т.е., поэтому их производительность будет примерно одинаковой. И очень многое также зависит от того как движок JS работает с памятью при создании и изменении массивов. Одно дело создавать массив с числами или строками без дырок и совершенно другой случай, когда в массиве встречаются дырки или данные другого типа. В спецификации для этих случаев создаются вообще разные виды массивов. И работа с ними будет существенно отличаться.
Увы чистого эксперимента не получится. Если же провести эксперимент с научной точностью то данное видео просто смотреть никто не будет. Повторял и буду повторять - есть фреймворк PHP Yii2. Его разрабочтик записывает видео длинной 3-4 часа наполненные очень крутой информацией, бесценной. У него 100-150 просмотров за много лет. Нужно понимать что даже shorts нарезанный с марвел набирает больше чем видео по программированию, поэтому приходится работать в сжатых рамках на грани "попса-программирование-популизм".
Смущает слайз, а не помещает ли он просто ссылку в переменную, вместо копирования? Для массива с примитивными данными подходит все, для глубокой структуры только стрингифай парс и структуредклон (жаль не был разобран)
Фантастическая скорость работы slice связана с оптимизацией Copy-on-Write. Эта оптимизация, при которой копирование больших данных откладывается до тех пор, пока одна из копий не будет изменена. То есть, по сути, создается не копия массива, а новая ссылка на память, в которой хранится ссылка на исходный массив. И лишь в момент изменения нового массива происходит фактическое копирование. Чтобы в этом убедиться, можно в цикле со slice после копирования производить операцию, изменяющую массив (например, push). Тогда вся магия исчезает и slice начинает работать не быстрее map или for.
Это очень правильный вопрос! ☝️ JIT-компиляторы современных JS-рантаймов могут оптимизировать выполнение некоторых сценариев при их многократном выполнении (например, несколько тысяч раз). Такие оптимизации, как loop unrolling (развертка циклов) или dead code elimination (удаление мертвого кода), могут существенно влиять на результаты бенчмарков или даже заметно их искажать. Использование литеральной записи массива вместо генерации массива со случайными данными также может добавить искажения в результаты бенчмарков. Литеральные массивы имеют фиксированную структуру, и их поведение более предсказуемо с точки зрения JIT-компилятора, что может сделать результаты менее репрезентативными для реальных сценариев с динамически изменяющимися данными. Таким образом, хотя результаты бенчмарков довольно интересны, но без учета таких факторов, как уровень оптимизации кода, прогрев JIT или предсказуемость структуры данных, их точность может быть сомнительной и не в полной мере отражать реальную производительность.
Потому что одинарный запуск такого кода, учитывая современные скорости, не даст понять разницу. Нужно будет увеличивать массив. Задача стоит не провести академический эксперимент, а обратить внимание на разницу скоростей и заставить людей задуматься над тем, что под капотом у методовов, как они работают.
У меня так получилось: slice_____________________________________________________2 concat___________________________________________________2 loop while (C изначально заданной длиной массива) __517 loop for (C изначально заданной длиной массива)____ 520 map____________________________________________________893 filter___________________________________________________1658 reduce_________________________________________________1753 loop for (через push())_________________________________1762 Array.from()____________________________________________11117 spread_________________________________________________15095 JSON.parse(JSON.stringify())___________________________15617 structuredClone________________________________________16090
Фантастическая скорость работы slice связана с оптимизацией Copy-on-Write. Эта оптимизация, при которой копирование больших данных откладывается до тех пор, пока одна из копий не будет изменена. То есть, по сути, создается не копия массива, а новая ссылка на память, в которой хранится ссылка на исходный массив. И лишь в момент изменения нового массива происходит фактическое копирование. Чтобы в этом убедиться, можно в цикле со slice после копирования производить операцию, изменяющую новый массив (например, push). Тогда вся магия исчезает и slice начинает работать не быстрее map или for.
а можно пожалуйста пример кода ? изменяя массив после копирования (10000 элементов) у меня slice (2s) а map(2.7s)
chatGPT объясняет что в slice не передается функция и оно не вызывается для каждого элемента по этому он быстрее
@@davidhayrapetyan7039 ну 2 секунды и 2,7 секунд это не такая большая разница. Slice, конечно, быстрее, чем map. Но не в тысячи раз.
Алексей в первом методе создания копии использовал push, что для данной задачи может быть оптимизированно, если написать это немного иначе, то результат будет получше:
for (let i = 0; i < 100000; i++) {
const m = new Array(array.length)
for (let k = 0; k < array.length; k++) m[k] = array[k]
}
Просто push() как будто добавляет в оригинальный массив элементы изменяя его длину, но на самом деле, это не совсем правда, при работе этого метода JS периодически создаёт новые массивы с удвоенной длиной когда длины нынешнего массива не хватает (JS изначально создаёт массивы с запасом длины). При этом приходится копировать элементы со старого массива в новый и запускать garbage collection что затратно. Если изначально создать массив нужной длины то время сократится вдвое.
Но конечно slice() тут всё равно вне конкуренции. И видео всё равно интересное и познавательное.
@@ВиталийБоднар-е1я огромное спасибо. Действительно не подумал по индексу. Насколько быстрее получается у вас?
Спасибо за уместное замечание. Хотя тут есть ещё вторая сторона медали. Мы конечно экономим на реалокациях массива в памяти. Но вот массивы созданные через конструктор Array (с указанием длины) определяются рантаймом V8 как разреженные. И при дальнейшей работе с этими массивами возможна ощутимая просадка производительности.
Поэтому поверхносное клонирование массива лучше таки делать через slice или даже через spread (как ни странно).
@@itgid не намного - в 2 раза быстрее чем при использовании push()
Чтобы спред опреатор в браузере работал быстрее в таких условиях, его можно использовать в таком виде - Array(arr.length).fill(...arr). Да, скорость все еще будет ниже чем у slice, но не в десятки тысяч раз, а, всего лишь, в сотни. А при однократном копировании одного огромного массива на 50 000 000 элементов разница и вовсе отсутствует, только в этом случае использовать нужно [...arr], иначе произойдет переполнение стека.
Concat?
Самое интересное, что результаты для браузера и для Node js отличаются)
//Node Js
3408 loop for
3339 loop while
1 spread
1 slice
1998 map
3331 filter
2 Array.from
25202 JSON.parse(JSON.stringify)
3 concat
1474 copy by index to new Array()
///
24624 structuredClone
184778 Object.assign
// Browser
2485 'loop for'
2455 'loop while'
21746 'spread'
3 'slice'
3870 'map'
2864 'filter'
16841 'Array.from'
16796 'JSON.parse(JSON.stringify)'
2 'concat'
622 'copy by index to new Array()'
///
15350 'structuredClone'
140473 'Object.assign'
Для чистоты эксперимента нужно движок JS было указать и его версию, т.к. в других движках могут присутствовать свои оптимизации и результат может кардинально отличаться.
Например, в V8 есть оптимизации для цикла for и while для большого количества итераций. В том же V8 итерационные методы массивов базируется на reducer-е, т.е., поэтому их производительность будет примерно одинаковой.
И очень многое также зависит от того как движок JS работает с памятью при создании и изменении массивов. Одно дело создавать массив с числами или строками без дырок и совершенно другой случай, когда в массиве встречаются дырки или данные другого типа. В спецификации для этих случаев создаются вообще разные виды массивов. И работа с ними будет существенно отличаться.
Увы чистого эксперимента не получится. Если же провести эксперимент с научной точностью то данное видео просто смотреть никто не будет. Повторял и буду повторять - есть фреймворк PHP Yii2. Его разрабочтик записывает видео длинной 3-4 часа наполненные очень крутой информацией, бесценной. У него 100-150 просмотров за много лет. Нужно понимать что даже shorts нарезанный с марвел набирает больше чем видео по программированию, поэтому приходится работать в сжатых рамках на грани "попса-программирование-популизм".
Смущает слайз, а не помещает ли он просто ссылку в переменную, вместо копирования? Для массива с примитивными данными подходит все, для глубокой структуры только стрингифай парс и структуредклон (жаль не был разобран)
Это легко проверить.Увеличить объем массива и сравнить время.
@@urakend а причем тут время? вопрос создает ли клон слайз или просто ссылается на текущий объект произведя нулевую мутацию
@@AlexGabber ,а притом,что во втором случае оно не измениться...
Всегда использовал slice вместо новомодных свистелок и, видимо, не зря. Но вообще странно что Array.from() такой медленный.
forEach то же, что и for?
А почему так происходит-то? Компилятор лучше оптимизирован для slice? или в чем дело?
Фантастическая скорость работы slice связана с оптимизацией Copy-on-Write. Эта оптимизация, при которой копирование больших данных откладывается до тех пор, пока одна из копий не будет изменена. То есть, по сути, создается не копия массива, а новая ссылка на память, в которой хранится ссылка на исходный массив. И лишь в момент изменения нового массива происходит фактическое копирование. Чтобы в этом убедиться, можно в цикле со slice после копирования производить операцию, изменяющую массив (например, push). Тогда вся магия исчезает и slice начинает работать не быстрее map или for.
Я взагалі в шоці 😮
вот у меня на 100 000.
Slice: 0.40 ms
Spread: 11.80 ms
Array.from: 6.10 ms
Concat: 0.60 ms
For Loop: 44.50 ms
зачем в цикле на 10 тысяч раз делать map, filter, spread и т.д.?
Это очень правильный вопрос! ☝️
JIT-компиляторы современных JS-рантаймов могут оптимизировать выполнение некоторых сценариев при их многократном выполнении (например, несколько тысяч раз). Такие оптимизации, как loop unrolling (развертка циклов) или dead code elimination (удаление мертвого кода), могут существенно влиять на результаты бенчмарков или даже заметно их искажать.
Использование литеральной записи массива вместо генерации массива со случайными данными также может добавить искажения в результаты бенчмарков. Литеральные массивы имеют фиксированную структуру, и их поведение более предсказуемо с точки зрения JIT-компилятора, что может сделать результаты менее репрезентативными для реальных сценариев с динамически изменяющимися данными.
Таким образом, хотя результаты бенчмарков довольно интересны, но без учета таких факторов, как уровень оптимизации кода, прогрев JIT или предсказуемость структуры данных, их точность может быть сомнительной и не в полной мере отражать реальную производительность.
Потому что одинарный запуск такого кода, учитывая современные скорости, не даст понять разницу. Нужно будет увеличивать массив. Задача стоит не провести академический эксперимент, а обратить внимание на разницу скоростей и заставить людей задуматься над тем, что под капотом у методовов, как они работают.
на 3 млн эл [...arr] заняло 521мс, на slice 125мс
У меня так получилось:
slice_____________________________________________________2
concat___________________________________________________2
loop while (C изначально заданной длиной массива) __517
loop for (C изначально заданной длиной массива)____ 520
map____________________________________________________893
filter___________________________________________________1658
reduce_________________________________________________1753
loop for (через push())_________________________________1762
Array.from()____________________________________________11117
spread_________________________________________________15095
JSON.parse(JSON.stringify())___________________________15617
structuredClone________________________________________16090