+ +
+ Разные трюки я тестировал на 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. + +
- Вы тоже прекращайте. + +
+
+ Играй в нашу новую игру прямо в Telegram! +
+ 

