Картинка, конечно, стронгли анрилейтед

Разные трюки я тестировал на Google Chrome 107.0.5304.107 и Mozilla Firefox 107.0 на Windows 10.

Чтобы результаты всегда были железно воспроизводимыми, я отключил все С-State’ы, ядра зафиксировал на 5 ГГц.

У меня 9900К, это Coffee Lake c AVX256, какие оптимизации применит Jit для вашего процессора — я не знаю, результат на вашем компьютере может отличаться от моего, в т.ч. из-за микроархитектуры процессора.

Скорость парсинга кода тоже входит в бенчмарк, поэтому браузер с быстрым парсером будет впереди.

Есть ли у переменной оверхед?


Есть ли смысл использовать только dot notation? Какова цена выноса лишней переменной?

var array = new Array(65535).fill()

// 3
var a = array.map((x) => x)
var b = array.map((x) => x)
var c = array.map((x) => x)

// 2
var a = array.map((x) => x)
var b = array.map((x) => x).map((x) => x)

// 1
var a = array.map((x) => x).map((x) => x).map((x) => x)

Чтобы узнать, гоняет ли браузер память туда-сюда, делаем мы .map на массив длиной 65535 с нулями внутри. Линк.

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

Хром не заметил разницы, а вот лиса заметила. Применительно к лисе, у лишней переменной есть измеряемый оверхед.

Есть ли разница между var, let, const или их отсутствием?



Проверим. Используя разные биндинги — создадим POJO с переменной е. Потом добавим ему функцию о и запустим эту функцию. Бенчмарк простой, но движущихся частей много. Линк.

var g = { e: [] }
g.o = function(x) { g.e.push(...[1,2,3]) }
g.o()

Код выглядит так, отличаются только биндинги.


Результат неожиданный, но железно воспроизводимый. var, быстрее.

Bounce pattern, Switch case, длинная тернарка


Если обе конструкции логически одинаковые, они должны строить одно и то же синтаксическое дерево, верно? Давайте проверим.

// switch case
function thing(e) {
    switch (e) {
      case 0:
        return "0";

      case 1:
        return "1";

      case 2:
        return "2";

      case 3:
        return "3";
        
      default:
        return "";
    }
}

// bounce pattern
function bounce(x)
{
   if (x === 0) return "0";
   if (x === 1) return "1";
   if (x === 2) return "2";
   if (x === 3) return "3";
   
   return ""
}

// ternary
function bounce(x) {
  return 0 === x ? "0" : 1 === x ? "1" : 2 === x ? "2" : 3 === x ? "3" : "";
}

Вот так выглядит код. Для всех вариантов он одинаков, отличаются только вызовы.

▍ 1. Вызов в цикле


for (let t = 0; 1e5 > t; t++) bounce(0), bounce(2), bounce(6);

Вызов выглядит так. Линк.


▍ 2. В цикле с другим типом


for (let t = 0; 1e5 > t; t++) bounce("0"), bounce("2"), bounce("");

Тут мы покидываем строку вместо числа. В свитче и if блоках используется строгое равенство, поэтому свитч выходит только через default, а if’ы выходят только последний return. Линк.


▍ 3. Без цикла


bounce(0), bounce(2), bounce(6)

Просто три вызова подряд, никаких циклов. Линк.


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

Также лиса, похоже, не строит одно и то же AST, как это делает хром. Рекомендую заменить ваши длинные if’ы и bounce паттерны на свитчи, чтобы избежать лисиных тормозов.

Инициализация массива


Для примера возьму из паттернов функционального программирования, когда ты инициализируешь массив, прокидывая лямбду в инициализатор. Просто ради примера, в качестве этой лямбды будет fizzbuzz.

var times = 65535;

function initializer(val, z) {
    const i = z % 5 | 0;
    return 0 == (z % 3 | 0) ? 0 === i ? "fizzbuzz" : "fizz" : 0 === i ? "buzz" : z;
}

// for i
var b = new Array(times);
for (var i = 0; i < times; i++) {
    b[i] = initializer(b[i], i)
}
b

// for push
var c = [];
for (var i = 0; i < times; i++) {
    c.push(initializer(c[i], i))
}
c

// Fill Map
new Array(times).fill().map(initializer)

Это не самый красивый fizzbuzz, но это мой fizzbuzz. Линк на бенч.


Вариант с fill map создаёт два массива, сначала при вызове конструктора, потом при вызове map. Но такой вариант безальтернативно быстрее на хроме.

Конкатенация массивов



// reduce
arr.reduce((acc, val) => acc.concat(val), [])

// flatMap
arr.flatMap(x => x)

// flat
arr.flat()

// reduce push
arr.reduce((acc, val) => {
    if (val) val.forEach(a => acc.push(a));
    return acc;
}, [])

// forEach push
let acc = [];

arr.forEach(val => {
    val && val.forEach(v => acc.push(v));
}), acc;

//concat spread
[].concat(...arr)

Конкатенация массивов на 1 уровень, поведение идентичное flat(1). Линк.


Иногда я не понимаю, почему разработчики движков оставили такой потенциал для оптимизации.

Уничтожение хрома


Бенчмарки ниже я перепроверял по нескольку раз, результат одинаковый и верный. Лиса действительно такая быстрая.

▍ Итерация по массиву


Сравнивать будем Array.prototype.forEach vs for...of vs for. На код смотрите по линку.


Ради производительности, циклы for, лучше переделать в forEach, чтобы хром не отставал.

▍ Содержит ли строка значение


// text.includes()
url.includes('matchthis')

// text.test()
/matchthis/.test(url)

// text.match()
url.match(/matchthis/).length >= 0

// text.indexOf()
url.indexOf('matchthis') >= 0

// text.search()
url.search('matchthis') >= 0



Трюк с IndexOf быстрее и на лисе, и на хроме. Используйте трюк с IndexOf. Линк на бенчмарк.

Преобразование строки в число


Тестируем неявное преобразование, парсинг и вызов конструктора.

// implicit
var imp = + strNum

// parseFloat
var toStr = parseFloat(strNum)

//Number
var num = Number(strNum)

▍ Int




Линк на бенч.

▍ Float




Я перепроверял, это не ошибка. Неявный каст стринги в инт практически бесплатный у лисы. Линк на бенч.

Выводы


  1. Лисичка похорошела.
  2. JS сделан за неделю на коленке.
  3. Я не пишу на JS.
  4. Вы тоже прекращайте.

Играй в нашу новую игру прямо в Telegram!

Комментарии (64)


  1. Aquahawk
    24.01.2023 12:14
    +8

    Не везде согласен с методологией, но сейчас ну совсем нет времени погружаться в детали, однако ещё в 2017 году я рассказывал (https://www.youtube.com/watch?v=I1LpqbzZmLM) почему таргечусь в ES3. Тогда, ради геттеров и сеттеров я быстро поднялся на ES5, и всё ещё моя рекомендация использовать все плюшки через Typescript и таргетиться в es5.


    1. Aquahawk
      24.01.2023 14:41
      +12

      Господа минусующее, вот без обид, но может кто-нибудь привести такой пример кода на ESNext который будучи странспилен в таргет ES5 будет медленнее? До сих пор я не видел ни одного такого примера. Почему если новая модная штука не даёт выигрыша в производительности стоит посылать её клиенту? Развитие языка это хорошо и правильно, конструкции новые многие удобные, но почему не оттранспилить их в более быстрое представление?

      В Typescript в угоду скорости используют вообще локальные переменные в скоупе, чтобы не обращаться к свойствам объектов, огребая миллион проблем, получая вот такой замечательный файл на 2.7 мегабайта кода и 2.5 тысячи функций в одном файле https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts

      И они не могут от этого уйти по причине производительности компилятора, я делал автоматизированный рефактор этого кода https://github.com/microsoft/TypeScript/issues/17861 и ребята гоняли тесты, производительность просела. С тех времён в тайпскрипте выразительные средства улучшились, и такой рефактор можно сделать красивее. Но они продолжают платить часами, днями и уже вероятно годами своих разработчиков за комфорт пользователя этого компилятора.

      Я не понимаю программистов которые в угоду моде заставляют расплачиваться пользователя, а потом тут появляются треды на 1000 комментов что софт дерьмо. Да он дерьмо ели его дерьмово делать.


      1. nin-jin
        24.01.2023 23:28
        +3

        может кто-нибудь привести такой пример кода на ESNext который будучи странспилен в таргет ES5 будет медленнее? До сих пор я не видел ни одного такого примера.

        https://perf.js.hyoo.ru/#!bench=71gc0c_i1yal9


        1. Aquahawk
          25.01.2023 09:00
          +8

          Чёт я ничерта не понял как этот тест работает, там inc никто не дёргает, вы проверяете скорость конструирования объектов? Дальнейшее рассмотрю из этого предположения. По коду у меня два вопроса по существу, зачем вы явно написали extends Object, это же бессмысленная ересь, и второе, синтаксис объявления полей который вы применили значительно замедляет этот странный бенчмарк.

          Я добавил к вашему бенчмарку немного дополнительных тестов, применив транспилер Typescript, а вы, похоже, применяли babel (тут могу ошибаться, но он любит через defineProperty объявлять, и я абсолютно не понимаю зачем это делать)

          https://perf.js.hyoo.ru/#!bench=ww1cau_g81ws7

          Выводы которые я вижу:

          1. Бессмысленное наследование Object очень портит результат в всех случаях.

          2. Объявления поля класса современным синтаксисом просаживает производительность вдвое

          3. Таки да, в 2023 году мы видим что наконец обычное объявление Class стало таки быстрее. Но поле всё портит.

          И тут интересно что когда я в 2017 году таки перешёл на ES5 (2009) прошло 8 лет с принятия стандарта, сегодня в 2023 прошло 7 лет с момента принятия es6(2015) в котором появились классы. И вот, наконец они быстры. Что интересно ни https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields ни https://github.com/tc39/proposal-class-fields не говорят в какой стандарт попали эти фичи. Но очередной раз я вижу как новый функционал в стандарте, даже спустя несколько лет после принятия тормозит. Должно пройти лет 7-10 чтобы он стал быстрым, и то не всегда.

          Таким образом я признаю, что действительно есть конструкция из ES6 быстрее ES5, и возможно действительно сегодня уже стоит таргетиться в ES6. Но не свежее. И дело не только в тормозах, но и в том что в мире ещё осталось значительное количество клиентов на 32bit windows 7 которые сидят на старом хроме. Зависит от масштаба вашего бизнеса, но если у вас миллионы пользователей и монетизация ненулевая то выясняется что просто саппорт старых браузеров, достигаемой одной строчкой в виде таргета компилятора приносит существенное количество денег. И в идеале можно по userAgent отдать два разных билда, но на такое изврат даже я не готов.

          P.S. а ещё что-то сломалось, вот тут https://perf.js.hyoo.ru/#!bench=twmjin_o4c5nl


          1. victor-homyakov
            25.01.2023 12:39
            +1

            Объявления поля класса современным синтаксисом просаживает производительность вдвое

            Не всё так страшно. Ну OK, создание класса с полем стало занимать микросекунду вместо 600 наносекунд. Обратите внимание - класса, а не экземпляра класса. Это действие, которое обычно происходит только один раз в самом начале выполнения приложения (и поэтому надо смотреть на холодный замер).

            Сколько классов может быть в современном жирном приложении, загружаемом в браузер? Если взять с потолка число в тысячу классов - с новым синтаксисом потребуется целая миллисекунда вместо 600 микросекунд. Даже если на старых компьютерах скорость выполнения в десятки раз медленнее - разница в скорости всё равно будет в десятках миллисекунд. Думаю, эта разница маловата, чтобы оправдать отказ от нового синтаксиса.

            Интереснее было бы побенчмаркать создание экземпляров этих разных классов и вызов методов. Вы могли бы это сделать?


            1. Aquahawk
              25.01.2023 12:48
              +1

              К сожалению снова погружаться в бенчмаркинг я не готов потому что нужно очень тщательно вновь разбираться в методологии предложенного инструмента и гонять множество тестов, это займёт несколько дней, у меня их нет. Тогда в 2017 на подготовку доклада ушло около 60 рабочих часов при том что я уже знал примерно что показывать. А сейчас я вообще в другом технологическом стеке и иногда вытираю слезу вспоминая простой и понятный мир js и превосходный typescript


            1. nin-jin
              25.01.2023 21:00
              +1

              Вы и сами легко можете это сделать: https://perf.js.hyoo.ru/#!bench=ovgci8_vzub6j


          1. CoolCmd
            25.01.2023 12:45
            +1

            Что интересно ни https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields ни https://github.com/tc39/proposal-class-fields не говорят в какой стандарт попали эти фичи.

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


            1. Aquahawk
              25.01.2023 14:05

              Ну вот лет через 7 может бцдет нормально, в 2030 обсудим :)


          1. victor-homyakov
            25.01.2023 13:05
            +1

            И тут интересно что когда я в 2017 году таки перешёл на ES5 (2009) прошло 8 лет с принятия стандарта, сегодня в 2023 прошло 7 лет с момента принятия es6(2015) в котором появились классы. И вот, наконец они быстры.

            Да, есть такая проблема. Многое новые языковые фичи сначала были реализованы как сахар поверх существующего языка, то есть буквально в коде на JS или вообще на специальном языке Torque, который движок выполнял "под капотом". Только потом, постепенно, самый часто используемый код оптимизировался, переводился на C++. Но успел образоваться замкнутый круг: разработчики не использовали условный bind, потому что рукописный полифилл заметно быстрее, а браузеры не оптимизировали bind, потому что его никто не использует.


            1. Aquahawk
              25.01.2023 14:04
              +1

              да, согласен что происходит именно это. Вопрос, зачем тогда тащить этит функционал в сам js, а не сосредоточиться только на его скорости и функционал который действительно даёт новые возможности типа тех же символов. Я за подход когда можно всё затакивать в typescrip или сделать диалект поверх babel, а сам низкойровневый js не раздувать.


              1. morijndael
                27.01.2023 11:11

                Или вообще подвинуть JS, и приделать, наконец к WebAssembly доступ к Web API. После уже можно выдохнуть с облегчением и переписать все на раст /ш

                А то получается комическая ситуация. Байткод, созданный для браузера может рулить полноценной операционной системой (см. WASI), но рулить браузером — нини, только через прослойки на JS и постоянную де/сериализацию всего туда-обратно


          1. nin-jin
            25.01.2023 20:40

            зачем вы явно написали extends Object, это же бессмысленная ересь

            1. Большинство реальных классов от кого-нибудь наследуются.

            2. Отладчик в Хроме показывает содержимое Symbol.toStringTag только для экзмепляров унаследованного класса.

            вы, похоже, применяли babel (тут могу ошибаться, но он любит через defineProperty объявлять, и я абсолютно не понимаю зачем это делать)

            Просто настройте TypeScript для совместимости с ESNext.

            что-то сломалось, вот тут https://perf.js.hyoo.ru/#!bench=twmjin_o4c5nl

            Что сломалось?


    1. Vadem
      24.01.2023 19:33
      +4

      Не понимаю почему вас минусуют. Тем более без аргументов.


  1. Cerberuser
    24.01.2023 13:05
    +1

    Результат неожиданный, но железно воспроизводимый. var, быстрее.

    На моём Chrome "железной воспроизводимости" не получилось - результат 1, результат 2, результат 3. Что-то с настройками, или просто тест такой, что в моей системе он заглушается шумом?


    1. programmerguru Автор
      24.01.2023 13:20

      Предполагаю, что да.

      Попробуйте залочить все ядра на одну частоту, отключить C-State'ы и установить план питания на макс. производительность.


      1. nin-jin
        24.01.2023 15:11
        +3

        Кто такие эти систейты и где они отключаются?


        1. programmerguru Автор
          24.01.2023 15:29
          +8

          Они с нами в одной комнате?


          Они в BIOS'e, если у вас ноутбук или "офисная" материнка, то сделать не получится.


          1. lorc
            24.01.2023 22:11
            +4

            cat 1 > /sys/devices/system/cpu/cpu*/cpuidle/state[1-3]/disableне поможет разве?

            Ну и scaling_governor выставить в performance само собой


      1. efkz
        25.01.2023 12:09
        +6

        А насколько ваш тест применим к реальной жизни?
        У юзеров ядра не залочены и C-State не отключены.


    1. victor-homyakov
      25.01.2023 02:08
      +12

      С настройками всё нормально. Если в бенчмарке для фронтенда, чтобы увидеть стабильную разницу, нужно играться с настройками железа - проблема не в настройках (99% пользователей этого не будут делать), а в самом бенчмарке, который пытается найти микроскопические отличия, легко съедаемые шумом и потому в большинстве случаев незаметные пользователю.


  1. kasyachitche
    24.01.2023 13:36

    Что на третьем рисунке изображено?


  1. LyuMih
    24.01.2023 13:45
    +14

    Статья опасная на мой взгляд. Новички в JS могут пойти в излишнюю оптимизацию вместо того, чтобы думать головой. Возьмут за основу раздел про if/else/switch или array.filter().map() и будут сувать куда не поподя эти правила "оптимизации". А ещё var быстрее let и const...

    Выводы из серии:

    1. Я не пишу на Java.

    2. Вы тоже прекращайте.


    1. ermouth
      24.01.2023 14:18
      +8

      Статья опасная на мой взгляд

      Так ведь любые знания опасны, мало ли как их будут использовать новички )

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

      Не всё, что в материале написано, будет работать в реальном коде прямо как на графиках – рантайм-оптимизация вообще очень тёмная и не всегда предсказуемая магия – но знать про масштабы этой магии под капотом точно не лишне.

      А ещё var быстрее let и const

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


  1. shuhray
    24.01.2023 15:56

    Лиса быстрее, но она теперь падает регулярно. Я не уходил с Лисы до последнего, пока не стала падать.


    1. aborouhin
      24.01.2023 17:54
      +16

      Лиса с сотнями вкладок (знаю, это не лучшая практика, но у меня отложенные на почитать потом страницы так и висят в виде вкладок, упорядоченных в папочки расширением Sidebery), 26 расширений, открыта практически круглосуточно. Судя по данным about:crashes, которые подтверждаются личными воспоминаниями, за 2022 год она вылетала 3 раза. ЧЯДНТ?


      1. domix32
        25.01.2023 11:56
        +1

        Честно говоря я удивлён, что люди ещё ловят краши без внешних форс мажоров. Мои "краши" обычно выглядит как kil -9 в сторону процесса или жёсткое отключение питания.

        Хотя я уже давно не пользовался 32-битными версиями.


      1. UMenyaNeudobnieVoprosiki
        25.01.2023 12:05

        Спасибо, даже не знал про эту вкладку. До недавнего падала вполне себе успешно, особенно в DevTools и при разработке всяких тяжёлых штук. Вот не далече как вчера пытался попрофайлить один фриз - колом вставала вкладка и у FF, и у хрома, висит, висит, а потом "а чо-та ничо не работает, давайте эту вкладку просто убьём". Т.е. технически FF не падает, но и сайтег не работает) ООМ, скорее всего, тоже не каждый раз посчитает, как и ситуации аля "сожрало 100% проца, всё висит, пришлось ресетнуть". Маппинг на отладочные штуки собранные через webpack на мою память никогда нормально не работал, ну т.е. он работает до первого изменения, а потом новые маппинги не подтягиваются, дебаж как хочешь.
        Ну и пришлось выкосить всё кроме vimium, instapaper.

        При этом переходить на хром всё равно не вариант, там бесит примерно всё, особенно отсутствие нормального Reader View


    1. Artyom_Silchenko
      24.01.2023 20:21
      +1

      Исходных данных мало. Пишу свой опыт: FF 109 x64 (последний ESR релиз, может другой номер), Win7 SP1 x64, 3 дополнения, более ста вкладак. FF падала раза 4. Скорее всего на каких то определенных сайтах "текут" вкладки. Сам пока не вычислил


    1. joffer
      24.01.2023 23:00
      +4

      0 падений за последние, наверное, года 3. Всегда использую последнюю официальную версию, ОС Ubuntu 22, постоянно открыто 30 - 40 вкладок, куча закладок, куча дополнений интегрировано.

      extensions


    1. Mikola-BLR
      25.01.2023 01:48
      +7

      За последние лет 7 довелось поработать на различных домашних / служебных компах - 3 ноутбука, 4 десктопа.

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

      Залез в БИОС, поигрался с настройками ОЗУ - тут же прекратила падать. Вот как рукой сняло проблему и потом за ещё год работы на этой машине по 5 дней в неделю по 8 часов - ни единого падения.

      Из чего я для себя сделал вывод, что проблема хардварная: из-за кривых настроек БИОСа память начинает давать ошибки, к которым Лиса очень чувствительна. Не зря на серверах используется ECC-память (error-correcting code memory, память с коррекцией ошибок).

      Придя домой, решил воспроизвести проблему на своём домашнем десктопе. Залез в БИОС и разогнал память выше указанного дефолтного значения 3000 МГц до 3200 МГц. Винда запускается, вроде бы всё нормально работает, а Лиса начинает падать каждые несколько минут. Вернул частоту к привычным 3000 МГц - стабильно работает до сих пор.

      Так что смотрите, что у вас с памятью в компе, не разогнана ли чрезмерно, не барахлит ли питание. Если есть в БИОСе возможность управлять разгоном, то снизьте частоту / добавьте напруги / увеличьте тайминги.


      1. samsdemon
        25.01.2023 09:56
        +1

        Может вашу проблему уже пофиксили) https://hacks.mozilla.org/2022/11/improving-firefox-stability-with-this-one-weird-trick/


    1. 0Bannon
      25.01.2023 01:54

      А у меня на ноутбуке линукс минт и фаерфокс. И почему-то фаерфокс при прокрутке плиток видео на ютубе, когда они подгружаются начинает дико тормозить. Решил на оперу перейти.


  1. nin-jin
    24.01.2023 16:04
    +3

    Ради производительности, циклы for, лучше переделать в forEach, чтобы хром не отставал.

    https://perf.js.hyoo.ru/#!bench=9w6t4a_4y41db

    Хром

    Лис

    ---

    Трюк с IndexOf быстрее и на лисе, и на хроме. Используйте трюк с IndexOf

    https://perf.js.hyoo.ru/#!bench=dn90ur_6qawpe

    Хром

    Лис

    ---

    Я перепроверял, это не ошибка. Неявный каст стринги в инт практически бесплатный у лисы.

    https://perf.js.hyoo.ru/#!bench=q4euna_ln9xk8

    Хром

    Лис

    ---

    Я не пишу на JS.

    Спасибо за полезные советы.


  1. event1
    24.01.2023 18:01
    +21

    Простите за глупый вопрос, а что нарисовано на графиках? Каковы единицы измерения?


    1. programmerguru Автор
      24.01.2023 18:22
      +2

      Количество выполнений кода в секунду, включая парсинг и компиляцию.


      1. san-smith
        24.01.2023 19:08
        +21

        Всю статью мучил тот же вопрос:)
        Может добавите в начало статьи пояснение?


        1. venanen
          25.01.2023 01:43
          +2

          Поддерживаю. Сначала думал, что это миллисекунды. Потом микросекунды. Думал, какой хром медленный однако, но когда там появились цифры с кучей нулей, я начал что-то подозревать


        1. programmerguru Автор
          25.01.2023 03:22
          +1

          Статью оптимизировал


  1. maeris
    24.01.2023 19:05
    +2

    Результат неожиданный, но железно воспроизводимый. var, быстрее.

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


  1. YuryB
    24.01.2023 20:15
    +4

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


    1. retry
      25.01.2023 10:21
      +1

      Методика конечно не строгая, а по поводу турбобуста явно же автор написал.

      >Чтобы результаты всегда были железно воспроизводимыми, я отключил все С-State’ы, ядра зафиксировал на 5 ГГц.


  1. victor-homyakov
    24.01.2023 22:38
    +7

    Есть ли у переменной оверхед?

    Во-первых, у меня ни на Intel ни на M1 не воспроизводится - отличия результатов находятся в пределах естественного разброса результатов бенчмарка, и в разных запусках рандомно лидирует разный вариант. Для проверки можно в одном бенчмарке сделать несколько тестов с одним и тем же количеством локальных переменных - между ними тоже будут заметные отличия.

    Во-вторых, если проанализировать результат автора (кстати, было бы не лишним указать погрешность измерений, которую показал бенчмарк):

    • один var и три map для массива на 65535 элементов занимают 1/345 секунды (345 ops/sec)

    • три var и три map для массива на 65535 элементов занимают 1/319 секунды (319 ops/sec)

    v + 3*m = 1/345
    3*v + 3*m = 1/319
    

    где v - длительность (оверхед) одного var, m - длительность одного map массива из 65535 элементов

    Получим v ≈ 118мс, m ≈ 927мс, то есть, утрируя, объявить девять лишних var - всё равно что обработать массив на 65 тысяч элементов? Объявление лишней промежуточной переменной не может быть настолько заметным. Кажется, этот бенчмарк измеряет что-то совсем другое.

    Есть ли разница между var, let, const или их отсутствием?

    Во-первых, не могу воспроизвести результат на Intel и M1 ни на бенчмарке автора, ни на своём. Самым быстрым в Firefox оказывается рандомно то const то вообще sloppy, а самый медленный результат отстаёт от него всего на 7-8 процентов.

    Если посмотреть на код бенчмарка

    var g = { e: [] }
    g.o = function(x) { g.e.push(...[1,2,3]) }
    g.o()
    

    то можно увидеть, что он пытается измерять множество вещей одновременно: инициализацию переменной, создание объекта, создание функции, вызов функции, spread operator, garbage collector в рандомные моменты времени, и много чего ещё. То есть на одно создание переменной приходится три чтения и много других операций. Даже если автор прав и корректные результаты показывает только его бенчмарк, то результат применим только в ситуации, когда каждая переменная читается всего несколько раз и потом выбрасывается. И даже в такой ситуации замена const на var даст ускорение всего лишь во втором знаке после запятой - с 21463673 ops/sec до 21843333 ops/sec, то есть экономию в 0.81 наносекунды на одно выполнение вышеприведённого кода. Потребуются миллиарды итераций, чтобы суммарная разница достигла заметной глазу пользователя величины.

    Конечно, теоретически разница не может равна нулю:

    • let в местах использования должен выполнять проверки на temporal dead zone и на область видимости, из-за этого он всегда немного медленнее, чем var

    • const должен выполнять проверки на область видимости и на temporal dead zone; как только константа будет проинициализирована, проверку на temporal dead zone можно больше не выполнять

    Но в приведённом бенчмарке я вижу, что вся разница тонет в шумах.

    В итоге ответ на вопрос "увижу ли я профит от использования var" сильно зависит от конкретного кода и от конкретной версии движка. Например, в Fastify поглядели на бенчмарки и позволили себе в большинстве мест заменить var на const и let - в свежих версиях Node.js разницы в скорости не видно.

    Поэтому на практике о такой мелочи можно не задумываться.

    P.S. Кстати, раз уж упомянул Fastify - полезно посмотреть на пулл-реквесты с оптимизациями производительности раз два


    1. sebres
      25.01.2023 17:27
      +1

      Совершенно верно... Вообще очень странные тесты...
      В конкретном случае - var vs let vs const vs sloppy там по большей части вообще измеряется spreading array mutations & push (+ память/кэш всех уровней, + GC, + тому подобное), но никак не заявленное в названии теста.

      Если переписать тест хотя бы без вызова push и spread-op (что справедливо ибо объявленная g всё равно immutable), всё очень сильно изменится (и соответственно возможно var будет быстрее только лишь sloppy, проигрывая и let и const):

      - g.o = function(x) { g.e.push(...[1,2,3]) }
      + g.o = function(x) { g.e.push }

      "Возможно" - потому что оверхед собственно измерения и соответственно шумы и погрешности будут много выше той незначительной разницы (если она вообще есть в SpiderMonkey или V8 при компиляции в браузере).

      <img src="боромир-мим.jpg"/> Нельзя просто взять и померить разницу в скорости var vs let vs const в JS в браузере, просто потому что там нет подходящего быстрого инструмента (цикла) для измерений, так чтобы при компиляции var, let или const результат не вырождался тупо в константное выражение, посчитанное на стадии компиляции цикла.

      Т.е. даже что-то подобное нижеследующему вряд ли покажет ту разницу реально (и те +13ms или +173ms ниже тупо не являются какими-либо флуктуациями опять же из-за огромного overhead сверху):

      // 1e9 итераций, const vs var:
      (() => { const x = 1, X = {f: function() {return x}}; var v = 0; performance.mark('m1'); for (let i = 1e9; i > 0; i--) { v += X.f()+X.f()+X.f()+X.f()+X.f(); } performance.mark('m2'); })(); performance.measure('test', 'm1', 'm2')
      ► PerformanceMeasure {..., duration: 1057}
      (() => { var   x = 1, X = {f: function() {return x}}; var v = 0; performance.mark('m1'); for (let i = 1e9; i > 0; i--) { v += X.f()+X.f()+X.f()+X.f()+X.f(); } performance.mark('m2'); })(); performance.measure('test', 'm1', 'm2')
      ► PerformanceMeasure {..., duration: 1070}
      
      // 1e10 итераций, const vs var:
      (() => { const x = 1, X = {f: function() {return x}}; var v = 0; performance.mark('m1'); for (let i = 1e10; i > 0; i--) { v += X.f()+X.f()+X.f()+X.f()+X.f(); } performance.mark('m2'); })(); performance.measure('test', 'm1', 'm2')
      ► PerformanceMeasure {..., duration: 13077.5}
      (() => { var   x = 1, X = {f: function() {return x}}; var v = 0; performance.mark('m1'); for (let i = 1e10; i > 0; i--) { v += X.f()+X.f()+X.f()+X.f()+X.f(); } performance.mark('m2'); })(); performance.measure('test', 'm1', 'm2')
      ► PerformanceMeasure {..., duration: 13250.1}


  1. victor-homyakov
    24.01.2023 23:23
    +21

    Преобразование строки в число

    Прямо классика

    TL;DR: Бенчмарк показывает фантастические результаты в Firefox только потому, что оптимизатор понимает: на вход всегда поступает одна и та же строка, отсутствуют вызовы функций с заранее неизвестными сайд-эффектами, а результат преобразования нигде не используется, значит все вычисления можно выбросить.

    Более правильный бенчмарк с минимальной попыткой подсунуть разные числа и хоть как-то использовать результат преобразования сразу возвращает с небес (1E+09 ops/sec) на землю.


  1. eboyar
    25.01.2023 01:56

    В чём смысл этой статьи? Сравнивать хром и лису только чтобы заключить "Я не пишу на JS и вам не советую"? Где нормальный вывод?


  1. rutexd
    25.01.2023 08:54
    -5

    Допустим. А что дальше? Лиса может быть и быстрее и лучше и даже лучше оптимизированее. Только ею пользоваться невозможно. Пишут свои веб стандарты которым на остальные все равно, ui тупит как в 2005 или хуже, по тестам на хабре же - сливает ещё больше данных чем хром.


    1. AlexM2001
      25.01.2023 10:20

      по тестам на хабре же - сливает ещё больше данных чем хром.

      Можно ссылку на такие тесты?


      1. rutexd
        25.01.2023 17:25
        +1

        к сожалению, того теста о котором я говорю, я сейчас уже не найду, ибо дело было в пределах 21-22 годов. В попытках найти нашел другую, хоть и намного менее конкретную статью но не менее занимательную. https://habr.com/ru/company/brave/blog/551588/

        Одна цифра в 2700 запросов и целый абзац с детальным списком отправляемой инфы уже говорит о "защищенности" и "приватности" "самого приватного и безопасного браузера". Нашел бы оригинальную статью о которой говорил - с радостью, отправил бы.



        1. AlexM2001
          25.01.2023 17:31

          Плюсы сегодня закончились)

          Спасибо за ссылку


    1. auresio
      25.01.2023 14:05
      +2

      Пишут свои веб стандарты

      Вы как то перепутали сторону, свои веб стандарты пишет гугл. А весь интернет радостно поддакивает. Вот только с адблоком прокол вышел.

      ui тупит как в 2005 или хуже

      Тут вообще без комментариев.


      1. rutexd
        25.01.2023 18:28
        -3

        Эти стандарты как минимум принимаются на широком уровне - оно и хорошо. Времена, когда один браузер умел одно, другой другое, третий ни того ни того а четвертый, извиняюсь, в песочнице жевал свои слюни - уже должны были давно пройти. Мы не в нулевых. Или хотя бы не при Медведеве. И гугл сделал очень многое для создания этой унификации. Но вместо этого у нас досих-пор 27 стандартов и половина из них легаси которая непонятно почему еще живет и орёт о уважении к себе.

        Если вы занимались разработкой сайтов, не используя новомодных технологий вроде вебпака или прочей лабуды, которая за вас 8\10 проблем решает, то вы должны знать насколько лиса капризна. Отсутствующие кодеки видео банально, другие именования вещей вроде стилей или тех самых кодеков при ручном указывании, даже изменить полоску прокрутки - целое событие над которым надо посидеть что бы оно заработало. Свои стандарты, вроде добавления сайтами в нативное меню кнопок. Вопрос зачем и почему. А потом приходит некто и спрашивает "ой а почему у меня кнопочки нету" после лисы. Свои апи в js, часть которых либо во всем остальном мире депрекейтед либо вообще оффициально не поддерживается (какой нибудь XPConnect , например, если мне не подводит память он все еще поддерживается в лисе или mozGetUserMedia условный. И да, ничего против обратной совместимости я не имею но не настолько)

        Если вы работали на средне-слабом \ старом железе в лисе, то вы знаете как безбожно она может тупить. Начиная от интерфейса который банально виснет от первого пука, заканчивая нагрузкой процессора при более десятка открытых вкладок. Не всегда и не во всех случаях но такое бывает время от времени. На хроме же - кроме потребления памяти на моей памяти ничего не было. Даже что бы заставить хром зависнуть - надо постараться. Хром еще с 2012-2014 когда я с ним впервые познакомился - летал. Для лисы же, для достижения подобной скорости и удобства, потребовался громкий релиз квантум (или как он там называется?). Однако на счет последнего я утерждать не берусь - слышал по осколкам. Как сейчас обстоят дела - врать не буду - не знаю - однако еще пару лет назад лично на моем опыте было в десяток раз больше проблем чем потенциальных плюсов.

        Кстати, по этому скорее беглому тесту нежели статье, потребление лисы далеко не самое эталонное в сравнение с каким нибудь хромом, несмотря на данную статью: https://habr.com/ru/post/589923/

        Если же не работали с сайтами или у вас никогда не было ничего древнего и бородатого старше чем на 5 лет - вам видимо просто очень повезло. К счастью или же нет.

        Альтернатива нужна. Но учитывая текущие реалии и масштабы горя, это крайне трудоёмкий или даже непосильный без всемасштабного движа процесс. И лиса хоть и пытается быть в тренде, проигрывает не просто так и не просто из за бытия в тени большого брата гугла.


  1. elvas7
    25.01.2023 10:20
    -1

    1. mayorovp
      25.01.2023 10:25

      У них даже на логотипе лиса...


      1. elvas7
        25.01.2023 11:10

        они сами не поняли, что имели ввиду - https://support.mozilla.org/en-US/questions/988854
        но именно firefox - это не лиса, а красная панда


    1. playermet
      25.01.2023 11:30

      Логотип Firefox конечно все более минималистичен с каждой новой версией, но как минимум первые десять лет на нем отчетливо узнается слева лиса, а справа огонь. Причем там даже пигментация морды прорисована, а так же характерные щеки, острые длинные рыжие уши, тонкие рыжие лапы, длинная морда, острый нос, хвост без полос и т.д. и т.п. Т.е. полное совпадение с тем, как выглядит обычная лиса, и полная противоположность тому, как выглядит красная панда.

      А если на логотипе лиса и огонь, то значит назван он в честь лисы и огня. Более того, он чисто исторически был сначала Phoenix, потом Firebird, а потом bird поменяли на fox, а fire всегда был центральной темой логотипа.


    1. nin-jin
      25.01.2023 21:12
      -1

      А вы в курсе, что морская свинка не имеет никакого отношения ни к морю, ни к свиньям?


      1. Alexufo
        26.01.2023 13:44
        -1

        Ну мы же не можем проверить обратного, потому что потерялся первоисточник, но это совсем не означает, что она не имеет отношения к морю и к свинье. Исторически может иметь совершенно.


  1. WVitek
    25.01.2023 12:22

    Сижу на Firefox ESR 32-bit. Памяти ест меньше, тормоза или нестабильности - исключительно редко.
    Единственное, что "сломалось" на каком-то из обновлений, так это Web UI торрент-клиента transmission (кнопки и меню не работают, в Edge при этом норм).


  1. rease
    25.01.2023 12:45
    +2

    К сервису measurethat, и к тем тестам, которые на нем пишут, вопросы возникали уже не раз. И некоторые Ваши тесты не исключение.

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

    По вашему тесту получается что, вместо того, чтобы просто создать массив и пройтись по нему один раз циклом, я сначала вызову метод "fill()", который внутри пройдет по нему циклом и заполнит "undefined", потом вызову "map(initializer)", который под капотом создаст новый массив и снова пройдет циклом заполняя его результатами функции "initializer". И вот это вот все будет работать в хроме быстрее В ВОСЕМЬ РАЗ (998 / 127)? Серьезно?

    На сколько мне известно, когда хром видит в коде стандартный цикл "for(...) {...}", он может оптимизировать код внутри блока так, как если бы это был код внутри функции, т.е. если нет сайд-эффектов, то он может полностью выпилить весь код в блоке. Но сам цикл он оставляет и будет его итерировать, т.к. это стандартная статическая языковая конструкция и она "невыпиливаемая". (Простой пример, написать "for(;true;);", и браузер успешно намертво зависнет).

    Когда вы тестируете "new Array(times).fill().map(initializer)", ваш массив никуда не возвращается и нигде больше не используется и для хрома, после оптимизации, является "мертвым", а далее вы вызываете встроенные в движок методы массива, который для хрома уже не более чем "мусор". И передаете функцию "initializer", которая не имеет сайд-эффектов и ее даже необязательно вызывать.

    Оптимизация)

    На деле же, все это будет работать раза в 4 медленнее.

    Добро пожаловать в реальный мир)))

    const times = 65535
    
    function initializer(val, z) {
      const i = z % 5 | 0
      return (z % 3 | 0) == 0 ? i === 0 ? 'fizzbuzz' : 'fizz' : i === 0 ? 'buzz' : z
    }
    
    function test_for() {
    // Bench for
      let b1
      console.time('bench for')
      for (let j = 0; j < 1000; j++) {
        b1 = new Array(times)
        for (let i = 0; i < times; i++) {
          b1[i] = initializer(b1[i], i)
        }
      }
      console.timeEnd('bench for')
      // console.log([b1])
      return b1
    }
    
    function test_map() {
    // Bench fill map
      let b2
      console.time('bench map')
      for (let j = 0; j < 1000; j++) {
        b2 = new Array(times).fill().map(initializer)
      }
      console.timeEnd('bench map')
      // console.log([b2])
      return b2
    }
    
    setTimeout(test_for), setTimeout(test_map)
    setTimeout(test_for), setTimeout(test_map)
    setTimeout(test_for), setTimeout(test_map)
    setTimeout(test_for), setTimeout(test_map)
    
    /*
    bench for: 568.27294921875 ms
    bench map: 1711.642822265625 ms
    
    bench for: 594.904052734375 ms
    bench map: 1728.125732421875 ms
    
    bench for: 478.802978515625 ms
    bench map: 1684.43798828125 ms
    
    bench for: 474.054931640625 ms
    bench map: 1763.044189453125 ms
    */
    

    .


    1. kahi4
      25.01.2023 13:23
      +2

      Поддерживаю.

      Для теста:

      ▍ Итерация по массиву

      Имеем результат на моем компьютере: for i x 153 ops/sec ±0.28% (63 runs sampled) в хроме и for i x 11,522 ops/sec ±0.51% (62 runs sampled) в firefox

      Открываем консольку, запускаем

      var array = new Array(65535).fill(0);
      var q = 0; 
      var t1 = performance.now();
      for (var repeat = 0; repeat < 100; repeat++) {
      for (var i = 0; i < array.length; i++) {
        q = array[i] * array[i];
      }
      }
      console.log(performance.now() - t1); 

      Ожидания:

      Хром: ~800ms (100 iterations * 153 it/sec)

      Firefox: ~ 1ms (100 iterations * 11 522 it / sec)

      Реальность:

      Хром: 14.5ms

      Firefox: 3799ms

      (Изначальный тест был с сотней повторов, но фаирфокс так вообще не умеет).

      Вывод: я знаю что запустить такой тест в консоли бессмысленно, консоль может выполняться без jit, быть непрогретой и тому подобное, но результаты консоли слишком сильно расходятся с результата бенчмарка, причем для хрома в 60 раз в пользу консоли, для FF в несколько тысяч раз в сторону бенчмарка. Короче: весь бенчмарк показывает близкие к случайным числам, поэтому никакие выводы на его основе делать я бы тоже не стал.


      1. rease
        25.01.2023 19:27
        +1

        Да согласен, потому что бенчмарки так не пишутся.

        Но проблема в том, что люди прочитав статью, могут это все принять за чистую монету, и потом пойдут все свои циклы "for" на "map" или "forEach" переписывать, думая что у них после этого перфоманс в 8 раз взлетит. Хотя в действительности так не будет.

        Действительность:

        const arr = new Array(65536).fill(0)
        
        function callback(v, k, a) {
          a[k] = v + k
        }
        
        function test_for() {
          console.time('bench     for')
          for (let j = 0; j < 1000; j++) {
            for (let i = 0; i < arr.length; i++) callback(arr[i], i, arr)
          }
          console.timeEnd('bench     for')
        }
        
        function test_forEach() {
          console.time('bench forEach')
          for (let j = 0; j < 1000; j++) {
            arr.forEach(callback)
          }
          console.timeEnd('bench forEach')
        }
        
        setTimeout(test_for), setTimeout(test_forEach)
        setTimeout(test_for), setTimeout(test_forEach)
        setTimeout(test_for), setTimeout(test_forEach)
        setTimeout(test_for), setTimeout(test_forEach)
        setTimeout(test_for), setTimeout(test_forEach)
        setTimeout(test_for), setTimeout(test_forEach)
        setTimeout(test_for), setTimeout(test_forEach)
        setTimeout(test_for), setTimeout(test_forEach)
        
        setTimeout(() => { console.log(arr) }, 100)
        
        
        
        // РЕЗУЛЬТАТ:
        /*
        bench     for: 478.824951171875 ms
        bench forEach: 973.968994140625 ms
        bench     for: 471.277099609375 ms
        bench forEach: 934.132080078125 ms
        bench     for: 124.044921875 ms
        bench forEach: 924.15087890625 ms
        bench     for: 128.7451171875 ms
        bench forEach: 929.00927734375 ms
        bench     for: 123.68798828125 ms
        bench forEach: 919.563232421875 ms
        bench     for: 123.55908203125 ms
        bench forEach: 150.320068359375 ms
        bench     for: 123.626953125 ms
        bench forEach: 148.734130859375 ms
        bench     for: 124.884033203125 ms
        bench forEach: 148.5390625 ms
        */


  1. YuryScript
    25.01.2023 19:51
    +4

    Такие бенчмарки не то что бесполезные, даже вредные. Лучше бы скрыть статью и забыть о ней, меньше вреда будет.


  1. ElvenSailor
    26.01.2023 16:17
    +1

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