Лінивий синтаксичний аналіз JavaScript в V8 – devSchacht – Medium

javascript

У цій статті ви дізнаєтеся: що таке лінивий синтаксичний аналіз, чим він корисний і які потенційні проблеми при його використанні.

Вихідні дані

Спочатку код перетворюється на список токенів, потім токени перетворюються на синтаксичне дерево, та був з цього дерева генерується машинний код.

Синтаксичний аналіз - це другий крок, перетворення токенів на абстрактне синтаксичне дерево (AST). Ось приклад вихідного коду з відповідним йому AST:

Попередній та повний синтаксичний аналіз

Тому замість аналізу кожної функції більшість функцій піддаються попередньому аналізу. Попередній аналіз виявляє синтаксичні помилки, але не дозволяє область видимості змінних, що використовуються у функції, і не генерує для неї AST.

Завдяки менш інтенсивному аналізу попередній синтаксичний аналіз приблизно в два рази швидше, ніж повний.

Однак, коли ви викликаєте функцію, яка ще не була повністю проаналізована, вам потрібно зробити повний аналіз під час її виклику.

Приклад із використанням V8

Давайте подивимося на приклад такої поведінки.

Node має спеціальний аргумент --trace_parse , при запуску з яким буде видно, як відбувається синтаксичний аналіз скриптів або функції. Тим не менш, висновок може бути досить великим через різний внутрішній код, який Node запускає для початкового завантаження вашої програми. Тому замість Node я використовуватиму оболонку V8 під назвою d8.

На відміну від Node, d8 не має функції console.log, тому я використовую функцію з ім'ям print:

Тут у мене дві функції sayHi і add, причому add ніколи не викликається.

Все ще є надлишковий висновок,пов'язаний з d8, але набагато менший, ніж якби ми використовували Node (при використанні d8 з останнього складання v8 надлишковий висновок відсутній, - прим. пров.). Важливими є три останні рядки.

Коли test.js спочатку аналізується, функції sayHi та add обробляються тільки попередньо, що прискорює аналіз вихідного коду.

Тоді ж, коли ми викликаємо sayHi, функція аналізується повністю.

Важливо: add ніколи не аналізується повністю. Це дозволяє економити час синтаксичного аналізатора та зменшує споживання пам'яті V8.

У чому проблема з лінивим синтаксичним розбором?

Давайте приберемо функцію add, що не використовується, з коду нижче.

Висновок d8 --trace_parse test.js :

Спочатку V8 попередньо обробляє sayHi, після чого слідує повний синтаксичний аналіз. Тоді попередній аналіз не потрібен, і наша програма працює швидше без спроб оптимізації V8!

У V8 є евристика, де функції, укладені в круглі дужки, завжди аналізуються повністю. Наприклад, це стосується негайно викликаних функціональних виразів (IIFE):

Зверніть увагу: тут немає parsing function: constants.

Тепер припустимо, що ми хочемо, щоб наш приклад sayHi працював швидше. Що ми можемо вдіяти?

V8 як і раніше виконує попередній парсинг sayHi , але ми можемо запобігти цьому, загорнувши вираз функції у круглі дужки.

Хоча це і не IIFE, V8 використовуватиме евристику і зробить повний аналіз із самого початку:

Optimize-JS

Замість того, щоб вручну робити ці оптимізації та перетворювати наш код на важко читаний, ми можемо використовувати інструмент optimize-js.

На практиці загальною причиною непотрібних попередніх аналізів є те, що мініфікаторUglifyJS видаляє круглі дужки з IIFE для збереження байтів:

Це не змінює поведінку коду, але порушує евристику Chrome.

Якщо ви запустите optimize-js на зменшеному коді, дужки будуть відновлені:

Чи варті ці оптимізації витрачених зусиль?

У документації optimize-js є результати кількох тестів продуктивності, що показують вражаючі прискорення - близько 20%. Однак у Chrome на моєму ноутбуці фактичне покращення для зразка програми на React складає всього 6 мс (час завантаження коду зменшився з 24 до 18 мс).

Швидше за все, є ефективніші речі, які ви можете зробити для підвищення продуктивності своїх сайтів. Але якщо у вас закінчилися ідеї, варто спробувати optimize-js та виміряти, чи є якесь значне покращення.