Обновить
2231.74
Рейтинг
RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR10

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

Время прочтения 4 мин
Просмотры 22K
Блог компании RUVDS.com Высокая производительность *JavaScript *Клиентская оптимизация *Браузеры
Картинка, конечно, стронгли анрилейтед

Разные трюки я тестировал на 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!
Теги:
Хабы:
Всего голосов 132: ↑116 и ↓16 +100
Комментарии 77 +77
Закрыть

Редакторский дайджест

Присылаем лучшие статьи раз в месяц

Комментарии 77

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds

Блог на Хабре