Pull to refresh

Убийцы оптимизации JS уже не такие страшные

Reading time4 min
Views19K
Год назад я увидела перевод Убийцы оптимизации, и была удивлена тем, сколько нужно держать в голове, чтобы писать оптимизированный js код. Особенно расстраивало, что практически весь es6 попадал под деоптимизацию.


И вот новый оптимизатор в v8, называемый TurboFan, за последний год научился оптимизировать этот самый практически весь es6, es5 и даже try-catch больше не является проблемой.

class TestClass {
    megaFunc() {
        try {
            let sum = 0;
            for (let val of [1, 2, 3]) {
                sum += val;
            }
            throw new Error(`sync error, sum = ${sum}`);
        }
        catch(err) {
            return err;
        }
    }
}
let test = new TestClass();
checkOptimizationStatus(test.megaFunc);

Function is optimized by TurboFan

Что осталось не оптимизированным, а так же как проверить свою функцию на предмет оптимизации или деоптимизации буквально в 1 действие можно увидеть под катом

2. Неподдерживаемый синтаксис


На данный момент не оптимизируются:

  • функции-генераторы;
  • функции, содержащие выражение for-of;
  • функции, содержащие выражение try-catch;
  • функции, содержащие выражение try-finally;
  • функции, содержащие составной оператор присваивания let;
  • функции, содержащие составной оператор присваивания const;
  • функции, содержащие объектные литералы, которые, в свою очередь, содержат объявления __proto__, get или set.

Скорее всего, неоптимизируемы:

  • функции, содержащие выражение debugger;
  • функции, вызывающие eval();
  • функции, содержащие выражение with.

Год назад этот список казался внушительным, на данный момент из списка не оптимизируется только debugger, генераторы и "__proto__, get или set", даже try-catch больше не требует трюка с tryCatch.

3. Использование arguments


Существует немало способов использовать arguments так, что оптимизировать функцию будет невозможно. Так что при работе с arguments следует быть особенно осторожными.
...

С arguments все просто, скорее всего их не будут оптимизировать, и уже достаточно легко можно перейти на rest параметры, с которыми можно работать как угодно.

4. Switch-case


Выражение switch-case на сегодняшний день может иметь до 128 пунктов case, и если превысить это количество, то содержащая данное выражение функция не сможет быть оптимизирована.

Теперь даже 500 case не вызывают деоптимизацию, 600 тоже.

5. For-in


Выражение For-in может несколькими способами помешать оптимизации функции. 5.1. Ключ не является локальной переменной
5.2. Итерируемый объект не является «простым перечисляемым»
5.2.2. В цепочке прототипов объекта есть поля с перечисляемыми значениями
5.2.3. Объект содержит перечисляемые индексы массива

Оптимизируется все, кроме ситуации когда key для for-in определен извне функции (но так всё равно никто писать не будет):

var key;
function nonLocalKey2() {
    var obj = {}
    for(key in obj);
}

6. Бесконечные циклы со сложной логикой условий выхода либо с неясными условиями выхода


Не удалось подобрать такой бесконечный цикл, чтобы получилась деоптимизация.

Как проверить свои функции на предмет оптимизации самостоятельно


Сделать это достаточно просто и для хрома и для ноды. В обоих случаях нужно всего лишь запустить их с флагом --allow-natives-syntax

Для chrome создаем ярлык:

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --js-flags="--allow-natives-syntax"

Файлы index.html

<script src="index.js"></script>

и index.js

function exampleFunction() {
    return 3;
    eval('');
}

checkOptimizationStatus(exampleFunction)

function checkOptimizationStatus(exampleFunction) {
    exampleFunction();
    exampleFunction();
    %OptimizeFunctionOnNextCall(exampleFunction);
    exampleFunction();

    switch (%GetOptimizationStatus(exampleFunction)) {
        case 1: console.log("Function is optimized"); break;
        case 2: console.log("Function is not optimized"); break;
        case 3: console.log("Function is always optimized"); break;
        case 4: console.log("Function is never optimized"); break;
        case 6: console.log("Function is maybe deoptimized"); break;
        case 7: console.log("Function is optimized by TurboFan " + exampleFunction.name); break;
        case 49: console.log("Function is optimized by NewMethod " + exampleFunction.name); break;
        default: console.log("Unknown optimization status"); break;
    }
}

И просто открываем index.html в браузере. Необходимости в веб-сервере нет, просто обычная html страничка.

Для node еще проще:

node --allow-natives-syntax index.js

Функция-обертка checkOptimizationStatus(yourFunction) покажет статус оптимизации, достаточно вызвать ее передав в качестве параметра вашу функцию

Итог


Из внушительного списка убийц осталось буквально 2-3 некритичных случая, которые далеко не каждый использует.
Новые оптимизация появляются достаточно быстро, поэтому можно просто выкинуть из головы этот список и спокойно писать на js в привычном/удобном стиле.

PS: В chrome 55 появилась поддержка async-await без флага, в ноде начиная с 8 ветки, промис-функции успешно оптимизируются, значит не долго ждать когда и async-await тоже будут оптимизироваться.

UPD: В chrome canary 57 добавлена оптимизация для async-await
Результаты для async-await функций

async function delayAsync(delay) {
	return new Promise(resolve => {
		setTimeout(() => resolve(), delay)
	})
}

async function asyncTest() {
    return 'habrahabr'
}

async function exampleFunction() {
    try {
        let result = await asyncTest()
        await delayAsync(500)
        console.log(`result after 500ms: ${result}`)
    }
    catch (err) {
        console.error(err)
    }
}


Function is optimized by TurboFan: exampleFunction
Function is optimized by TurboFan: asyncTest
Function is optimized by TurboFan: delayAsync
//ждем 500ms, после этого результат
(3) after 500ms: habrahabr

Tags:
Hubs:
Total votes 61: ↑58 and ↓3+55
Comments71

Articles