Разные трюки я тестировал на 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
Я перепроверял, это не ошибка. Неявный каст стринги в инт практически бесплатный у лисы. Линк на бенч.
Выводы
- Лисичка похорошела.
- JS сделан за неделю на коленке.
- Я не пишу на JS.
- Вы тоже прекращайте.
Автор:
programmerguru