<div><div> <span>4 мин</span> </div><div><span title="Количество просмотров"> <span>22K</span></span> </div><img src="https://habrastorage.org/webt/ai/z5/gz/aiz5gztttvvkegk_annvvgtumv8.png"/><div><i><span>Картинка, конечно, стронгли анрилейтед</span></i><br/> <br/> Разные трюки я тестировал на Google Chrome 107.0.5304.107 и Mozilla Firefox 107.0 на Windows 10.<br/> <br/> Чтобы результаты всегда были железно воспроизводимыми, я отключил все С-State’ы, ядра зафиксировал на 5 ГГц.<br/> <br/> У меня 9900К, это Coffee Lake c AVX256, какие оптимизации применит Jit для вашего процессора — я не знаю, результат на вашем компьютере может отличаться от моего, в т.ч. из-за микроархитектуры процессора.<br/> <br/> Скорость парсинга кода тоже входит в бенчмарк, поэтому браузер с быстрым парсером будет впереди.<br/> <br/> </div><h2> <span>Есть ли у переменной оверхед?</span> </h2><div><br/> Есть ли смысл использовать только dot notation? Какова цена выноса лишней переменной?<br/> <br/> </div><pre><code><span>var</span> array = <span>new</span> <span>Array</span>(<span>65535</span>).fill() <span>var</span> a = array.map(<span>(<span>x</span>) =></span> x) <span>var</span> b = array.map(<span>(<span>x</span>) =></span> x) <span>var</span> c = array.map(<span>(<span>x</span>) =></span> x) <span>var</span> a = array.map(<span>(<span>x</span>) =></span> x) <span>var</span> b = array.map(<span>(<span>x</span>) =></span> x).map(<span>(<span>x</span>) =></span> x) <span>var</span> a = array.map(<span>(<span>x</span>) =></span> x).map(<span>(<span>x</span>) =></span> x).map(<span>(<span>x</span>) =></span> x)</code></pre><div><br/> Чтобы узнать, гоняет ли браузер память туда-сюда, делаем мы <code>.map</code> на массив длиной 65535 с нулями внутри. <a href="https://www.measurethat.net/Benchmarks/Show/22097/0/1-var-vs-2-vars-vs-3-vars">Линк</a>.<br/> <br/> <i><span>Здесь и далее в качестве единицы измерения на графиках будет указано кол-во выполнений кода в секунду, включая парсинг и компиляцию. Больше — лучше</span></i><br/> <br/> Хром не заметил разницы, а вот лиса заметила. Применительно к лисе, у лишней переменной есть измеряемый оверхед.<br/> <br/> </div><h2> <span>Есть ли разница между var, let, const или их отсутствием?</span> </h2><div><br/> <br/> Проверим. Используя разные биндинги — создадим POJO с переменной <code>е</code>. Потом добавим ему функцию <code>о</code> и запустим эту функцию. Бенчмарк простой, но движущихся частей много. <a href="https://www.measurethat.net/Benchmarks/Show/22083/0/const-vs-let-vs-var-vs-sloppy">Линк</a>.<br/> <br/> </div><pre><code><span>var</span> g = { <span>e</span>: [] } g.o = <span><span>function</span>(<span>x</span>) </span>{ g.e.push(...[<span>1</span>,<span>2</span>,<span>3</span>]) } g.o()</code></pre><div><br/> Код выглядит так, отличаются только биндинги.<br/> <br/> <br/> Результат неожиданный, но железно воспроизводимый. <code>var</code>, быстрее.<br/> <br/> </div><h2> <span>Bounce pattern, Switch case, длинная тернарка</span> </h2><div><br/> Если обе конструкции логически одинаковые, они должны строить одно и то же синтаксическое дерево, верно? Давайте проверим.<br/> <br/> </div><pre><code> <span><span>function</span> <span>thing</span>(<span>e</span>) </span>{ <span>switch</span> (e) { <span>case</span> <span>0</span>: <span>return</span> <span>"0"</span>; <span>case</span> <span>1</span>: <span>return</span> <span>"1"</span>; <span>case</span> <span>2</span>: <span>return</span> <span>"2"</span>; <span>case</span> <span>3</span>: <span>return</span> <span>"3"</span>; <span>default</span>: <span>return</span> <span>""</span>; } } <span><span>function</span> <span>bounce</span>(<span>x</span>) </span>{ <span>if</span> (x === <span>0</span>) <span>return</span> <span>"0"</span>; <span>if</span> (x === <span>1</span>) <span>return</span> <span>"1"</span>; <span>if</span> (x === <span>2</span>) <span>return</span> <span>"2"</span>; <span>if</span> (x === <span>3</span>) <span>return</span> <span>"3"</span>; <span>return</span> <span>""</span> } <span><span>function</span> <span>bounce</span>(<span>x</span>) </span>{ <span>return</span> <span>0</span> === x ? <span>"0"</span> : <span>1</span> === x ? <span>"1"</span> : <span>2</span> === x ? <span>"2"</span> : <span>3</span> === x ? <span>"3"</span> : <span>""</span>; }</code></pre><div><br/> Вот так выглядит код. Для всех вариантов он одинаков, отличаются только вызовы.<br/> <br/> </div><h3> <span>▍ 1. Вызов в цикле</span> </h3><pre><code><span>for</span> (<span>let</span> t = <span>0</span>; <span>1e5</span> > t; t++) bounce(<span>0</span>), bounce(<span>2</span>), bounce(<span>6</span>);</code></pre><div><br/> Вызов выглядит так. <a href="https://www.measurethat.net/Benchmarks/Show/22099/0/no-type-coercion-switch-case-vs-bounce-pattern-vs-terna">Линк</a>.<br/> <br/> <br/> </div><h3> <span>▍ 2. В цикле с другим типом</span> </h3><pre><code><span>for</span> (<span>let</span> t = <span>0</span>; <span>1e5</span> > t; t++) bounce(<span>"0"</span>), bounce(<span>"2"</span>), bounce(<span>""</span>);</code></pre><div><br/> Тут мы покидываем строку вместо числа. В свитче и <code>if</code> блоках используется строгое равенство, поэтому свитч выходит только через <code>default</code>, а <code>if</code>’ы выходят только последний <code>return</code>. <a href="https://www.measurethat.net/Benchmarks/Show/22098/0/type-coercion-switch-case-vs-bounce-pattern-vs-ternary">Линк</a>.<br/> <br/> <br/> </div><h3> <span>▍ 3. Без цикла</span> </h3><pre><code>bounce(<span>0</span>), bounce(<span>2</span>), bounce(<span>6</span>)</code></pre><div><br/> Просто три вызова подряд, никаких циклов. <a href="https://www.measurethat.net/Benchmarks/Show/22100/0/no-loop-switch-case-vs-bounce-pattern-vs-ternary">Линк</a>.<br/> <br/> <br/> Похоже, что после первоначальной компиляции лиса не пытается дальше оптимизировать цикл, как это делает хром.<br/> <br/> Также лиса, похоже, не строит одно и то же AST, как это делает хром. Рекомендую заменить ваши длинные <code>if</code>’ы и bounce паттерны на свитчи, чтобы избежать лисиных тормозов.<br/> <br/> </div><h2> <span>Инициализация массива</span> </h2><div><br/> Для примера возьму из паттернов функционального программирования, когда ты инициализируешь массив, прокидывая лямбду в инициализатор. Просто ради примера, в качестве этой лямбды будет fizzbuzz.<br/> <br/> </div><pre><code><span>var</span> times = <span>65535</span>; <span><span>function</span> <span>initializer</span>(<span>val, z</span>) </span>{ <span>const</span> i = z % <span>5</span> | <span>0</span>; <span>return</span> <span>0</span> == (z % <span>3</span> | <span>0</span>) ? <span>0</span> === i ? <span>"fizzbuzz"</span> : <span>"fizz"</span> : <span>0</span> === i ? <span>"buzz"</span> : z; } <span>var</span> b = <span>new</span> <span>Array</span>(times); <span>for</span> (<span>var</span> i = <span>0</span>; i < times; i++) { b[i] = initializer(b[i], i) } b <span>var</span> c = []; <span>for</span> (<span>var</span> i = <span>0</span>; i < times; i++) { c.push(initializer(c[i], i)) } c <span>new</span> <span>Array</span>(times).fill().map(initializer) </code></pre><div><br/> Это не самый красивый fizzbuzz, но это мой fizzbuzz. <a href="https://www.measurethat.net/Benchmarks/Show/22086/0/array-initialization-for-for-push-fill-map">Линк на бенч</a>.<br/> <br/> <br/> Вариант с <code>fill map</code> создаёт два массива, сначала при вызове конструктора, потом при вызове map. Но такой вариант безальтернативно быстрее на хроме.<br/> <br/> </div><h2> <span>Конкатенация массивов</span> </h2><pre><code> arr.reduce(<span>(<span>acc, val</span>) =></span> acc.concat(val), []) arr.flatMap(<span><span>x</span> =></span> x) arr.flat() arr.reduce(<span>(<span>acc, val</span>) =></span> { <span>if</span> (val) val.forEach(<span><span>a</span> =></span> acc.push(a)); <span>return</span> acc; }, []) <span>let</span> acc = []; arr.forEach(<span><span>val</span> =></span> { val && val.forEach(<span><span>v</span> =></span> acc.push(v)); }), acc; [].concat(...arr) </code></pre><div><br/> Конкатенация массивов на 1 уровень, поведение идентичное <code>flat(1)</code>. <a href="https://www.measurethat.net/Benchmarks/Show/22085/0/flat-vs-flatmap-vs-reduce-vs-reduce-push-vs-foreach-pus">Линк</a>.<br/> <br/> <br/> Иногда я не понимаю, почему разработчики движков оставили такой потенциал для оптимизации.<br/> <br/> </div><h2> <span>Уничтожение хрома</span> </h2><div><br/> Бенчмарки ниже я перепроверял по нескольку раз, результат одинаковый и верный. Лиса действительно такая быстрая.<br/> <br/> </div><h3> <span>▍ Итерация по массиву</span> </h3><div><br/> Сравнивать будем <code>Array.prototype.forEach vs for...of vs for</code>. На код смотрите <a href="https://www.measurethat.net/Benchmarks/Show/22095/0/side-effect-for-i-vs-for-of-vs-foreach-fix">по линку</a>.<br/> <br/> <br/> Ради производительности, циклы <code>for</code>, лучше переделать в <code>forEach</code>, чтобы хром не отставал.<br/> <br/> </div><h3> <span>▍ Содержит ли строка значение</span> </h3><pre><code> url.includes(<span>'matchthis'</span>) /matchthis/.test(url) url.match(<span>/matchthis/</span>).length >= <span>0</span> url.indexOf(<span>'matchthis'</span>) >= <span>0</span> url.search(<span>'matchthis'</span>) >= <span>0</span> </code></pre><div><br/> <br/> <br/> Трюк с <code>IndexOf</code> быстрее и на лисе, и на хроме. Используйте трюк с <code>IndexOf</code>. <a href="https://www.measurethat.net/Benchmarks/Show/22090/0/includes-vs-test-vs-match-vs-indexof-vs-search-fix">Линк на бенчмарк</a>.<br/> <br/> </div><h2> <span>Преобразование строки в число</span> </h2><div><br/> Тестируем неявное преобразование, парсинг и вызов конструктора.<br/> <br/> </div><pre><code> <span>var</span> imp = + strNum <span>var</span> toStr = <span>parseFloat</span>(strNum) <span>var</span> num = <span>Number</span>(strNum) </code></pre><div><br/> <br/> <br/> <a href="https://www.measurethat.net/Benchmarks/Show/21897/0/implicit-vs-parseint-vs-number-string-to-num">Линк на бенч</a>.<br/> <br/> </div><h3> <span>▍ Float</span> </h3><div><br/> <br/> <br/> Я перепроверял, это не ошибка. Неявный каст стринги в инт практически бесплатный у лисы. <a href="https://www.measurethat.net/Benchmarks/Show/22092/0/implicit-vs-parsefloat-vs-number-string-to-num">Линк на бенч</a>.<br/> <br/> </div><h2> <span>Выводы</span> </h2><ol><li>Лисичка похорошела.</li><li>JS сделан за неделю на коленке.</li><li>Я не пишу на JS.</li><li>Вы тоже прекращайте.</li></ol></div>