Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень

в 9:00, , рубрики: Google Chrome, javascript, Mozilla Firefox, ruvds_статьи, Блог компании RUVDS.com, браузеры, высокая производительность, Клиентская оптимизация, оптимизация

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 1

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

Разные трюки я тестировал на 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 с нулями внутри. Линк.

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 2

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

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

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 3

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

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

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

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 4

Результат неожиданный, но железно воспроизводимый. 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);

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

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 5

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

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

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

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 6

▍ 3. Без цикла

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

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

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 7

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

Также лиса, похоже, не строит одно и то же 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. Линк на бенч.

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 8

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

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

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 9

// 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). Линк.

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 10

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

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

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

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

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

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 11

Ради производительности, циклы 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
Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 12

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

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

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

// implicit
var imp = + strNum

// parseFloat
var toStr = parseFloat(strNum)

//Number
var num = Number(strNum)

▍ Int

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 13

Линк на бенч.

▍ Float

Js, трюки, наблюдения, бенчмарки и как Лиса уничтожает Хром. Я протестировал всё, что вам было лень - 14

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

Выводы

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

Автор:
programmerguru

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js