Сравниваем скорости создания копий массивов в JavaScript разными способами

แชร์
ฝัง
  • เผยแพร่เมื่อ 24 ม.ค. 2025

ความคิดเห็น • 27

  • @KadochnikovK
    @KadochnikovK หลายเดือนก่อน +2

    Фантастическая скорость работы slice связана с оптимизацией Copy-on-Write. Эта оптимизация, при которой копирование больших данных откладывается до тех пор, пока одна из копий не будет изменена. То есть, по сути, создается не копия массива, а новая ссылка на память, в которой хранится ссылка на исходный массив. И лишь в момент изменения нового массива происходит фактическое копирование. Чтобы в этом убедиться, можно в цикле со slice после копирования производить операцию, изменяющую новый массив (например, push). Тогда вся магия исчезает и slice начинает работать не быстрее map или for.

    • @davidhayrapetyan7039
      @davidhayrapetyan7039 หลายเดือนก่อน

      а можно пожалуйста пример кода ? изменяя массив после копирования (10000 элементов) у меня slice (2s) а map(2.7s)
      chatGPT объясняет что в slice не передается функция и оно не вызывается для каждого элемента по этому он быстрее

    • @KadochnikovK
      @KadochnikovK หลายเดือนก่อน

      @@davidhayrapetyan7039 ну 2 секунды и 2,7 секунд это не такая большая разница. Slice, конечно, быстрее, чем map. Но не в тысячи раз.

  • @ВиталийБоднар-е1я
    @ВиталийБоднар-е1я หลายเดือนก่อน +5

    Алексей в первом методе создания копии использовал 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() тут всё равно вне конкуренции. И видео всё равно интересное и познавательное.

    • @itgid
      @itgid  หลายเดือนก่อน

      @@ВиталийБоднар-е1я огромное спасибо. Действительно не подумал по индексу. Насколько быстрее получается у вас?

    • @SerzhNesteruk
      @SerzhNesteruk หลายเดือนก่อน +1

      Спасибо за уместное замечание. Хотя тут есть ещё вторая сторона медали. Мы конечно экономим на реалокациях массива в памяти. Но вот массивы созданные через конструктор Array (с указанием длины) определяются рантаймом V8 как разреженные. И при дальнейшей работе с этими массивами возможна ощутимая просадка производительности.
      Поэтому поверхносное клонирование массива лучше таки делать через slice или даже через spread (как ни странно).

    • @ВиталийБоднар-е1я
      @ВиталийБоднар-е1я หลายเดือนก่อน

      @@itgid не намного - в 2 раза быстрее чем при использовании push()

  • @KadochnikovK
    @KadochnikovK หลายเดือนก่อน

    Чтобы спред опреатор в браузере работал быстрее в таких условиях, его можно использовать в таком виде - Array(arr.length).fill(...arr). Да, скорость все еще будет ниже чем у slice, но не в десятки тысяч раз, а, всего лишь, в сотни. А при однократном копировании одного огромного массива на 50 000 000 элементов разница и вовсе отсутствует, только в этом случае использовать нужно [...arr], иначе произойдет переполнение стека.

  • @zestlife5792
    @zestlife5792 หลายเดือนก่อน +3

    Concat?

  • @xthemey
    @xthemey หลายเดือนก่อน +4

    Самое интересное, что результаты для браузера и для 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'

  • @ИванГоденов-и7д
    @ИванГоденов-и7д หลายเดือนก่อน +2

    Для чистоты эксперимента нужно движок JS было указать и его версию, т.к. в других движках могут присутствовать свои оптимизации и результат может кардинально отличаться.
    Например, в V8 есть оптимизации для цикла for и while для большого количества итераций. В том же V8 итерационные методы массивов базируется на reducer-е, т.е., поэтому их производительность будет примерно одинаковой.
    И очень многое также зависит от того как движок JS работает с памятью при создании и изменении массивов. Одно дело создавать массив с числами или строками без дырок и совершенно другой случай, когда в массиве встречаются дырки или данные другого типа. В спецификации для этих случаев создаются вообще разные виды массивов. И работа с ними будет существенно отличаться.

    • @itgid
      @itgid  หลายเดือนก่อน

      Увы чистого эксперимента не получится. Если же провести эксперимент с научной точностью то данное видео просто смотреть никто не будет. Повторял и буду повторять - есть фреймворк PHP Yii2. Его разрабочтик записывает видео длинной 3-4 часа наполненные очень крутой информацией, бесценной. У него 100-150 просмотров за много лет. Нужно понимать что даже shorts нарезанный с марвел набирает больше чем видео по программированию, поэтому приходится работать в сжатых рамках на грани "попса-программирование-популизм".

  • @AlexGabber
    @AlexGabber หลายเดือนก่อน +3

    Смущает слайз, а не помещает ли он просто ссылку в переменную, вместо копирования? Для массива с примитивными данными подходит все, для глубокой структуры только стрингифай парс и структуредклон (жаль не был разобран)

    • @urakend
      @urakend หลายเดือนก่อน

      Это легко проверить.Увеличить объем массива и сравнить время.

    • @AlexGabber
      @AlexGabber หลายเดือนก่อน

      @@urakend а причем тут время? вопрос создает ли клон слайз или просто ссылается на текущий объект произведя нулевую мутацию

    • @urakend
      @urakend หลายเดือนก่อน

      @@AlexGabber ,а притом,что во втором случае оно не измениться...

  • @swayok
    @swayok หลายเดือนก่อน +1

    Всегда использовал slice вместо новомодных свистелок и, видимо, не зря. Но вообще странно что Array.from() такой медленный.

  • @Nine_Tails
    @Nine_Tails หลายเดือนก่อน

    forEach то же, что и for?

  • @_AnthonyD_
    @_AnthonyD_ หลายเดือนก่อน +1

    А почему так происходит-то? Компилятор лучше оптимизирован для slice? или в чем дело?

    • @KadochnikovK
      @KadochnikovK หลายเดือนก่อน +2

      Фантастическая скорость работы slice связана с оптимизацией Copy-on-Write. Эта оптимизация, при которой копирование больших данных откладывается до тех пор, пока одна из копий не будет изменена. То есть, по сути, создается не копия массива, а новая ссылка на память, в которой хранится ссылка на исходный массив. И лишь в момент изменения нового массива происходит фактическое копирование. Чтобы в этом убедиться, можно в цикле со slice после копирования производить операцию, изменяющую массив (например, push). Тогда вся магия исчезает и slice начинает работать не быстрее map или for.

  • @ГаляКравченко-у2ш
    @ГаляКравченко-у2ш หลายเดือนก่อน +2

    Я взагалі в шоці 😮

  • @1654045
    @1654045 หลายเดือนก่อน

    вот у меня на 100 000.
    Slice: 0.40 ms
    Spread: 11.80 ms
    Array.from: 6.10 ms
    Concat: 0.60 ms
    For Loop: 44.50 ms

  • @Tar2ga
    @Tar2ga หลายเดือนก่อน +2

    зачем в цикле на 10 тысяч раз делать map, filter, spread и т.д.?

    • @SerzhNesteruk
      @SerzhNesteruk หลายเดือนก่อน

      Это очень правильный вопрос! ☝️
      JIT-компиляторы современных JS-рантаймов могут оптимизировать выполнение некоторых сценариев при их многократном выполнении (например, несколько тысяч раз). Такие оптимизации, как loop unrolling (развертка циклов) или dead code elimination (удаление мертвого кода), могут существенно влиять на результаты бенчмарков или даже заметно их искажать.
      Использование литеральной записи массива вместо генерации массива со случайными данными также может добавить искажения в результаты бенчмарков. Литеральные массивы имеют фиксированную структуру, и их поведение более предсказуемо с точки зрения JIT-компилятора, что может сделать результаты менее репрезентативными для реальных сценариев с динамически изменяющимися данными.
      Таким образом, хотя результаты бенчмарков довольно интересны, но без учета таких факторов, как уровень оптимизации кода, прогрев JIT или предсказуемость структуры данных, их точность может быть сомнительной и не в полной мере отражать реальную производительность.

    • @itgid
      @itgid  หลายเดือนก่อน

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

    • @АлексейСоколов-у3к
      @АлексейСоколов-у3к หลายเดือนก่อน +1

      на 3 млн эл [...arr] заняло 521мс, на slice 125мс

  • @1SkinneR111
    @1SkinneR111 หลายเดือนก่อน

    У меня так получилось:
    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