Мързеливо анализиране на JavaScript във V8 – devSchacht – Среден
В тази статия ще научите какво е отложено анализиране, защо е полезно и какви са потенциалните проблеми при използването му.
Изходни данни
Първо кодът се преобразува в списък от токени, след това токените се преобразуват в синтактично дърво и след това от това дърво се генерира машинен код.
Парсирането е втората стъпка, преобразувайки токените в абстрактно синтактично дърво (AST). Ето примерен изходен код със съответния AST:
Предварителен анализ и пълен анализ
Следователно, вместо да се анализира всяка характеристика, повечето характеристики се подлагат на предварителен анализ. Подготовката открива синтактични грешки, но не разрешава обхвата на променливите, използвани във функцията, и не генерира AST за нея.
Поради по-малко интензивния анализ, предварителният анализ е около два пъти по-бърз от пълния анализ.
Въпреки това, когато извиквате функция, която все още не е напълно анализирана, трябва да направите пълен анализ в момента, в който се извиква.
Пример с V8
Нека да разгледаме пример за такова поведение.
Node има специален --trace_parse аргумент, който, когато се изпълни, ще ви покаже как се анализират скриптове или функции. Резултатът обаче може да бъде доста голям поради различния вътрешен код, който Node изпълнява, за да стартира вашата програма. Така че вместо Node, ще използвам V8 обвивка, наречена d8.
За разлика от Node, d8 няма функция console.log, така че използвам функция, наречена print:
Тук имам две функции sayHi и add и add никога не се извиква.
Все още има излишен изход,свързани с d8, но много по-малки, отколкото ако използвахме Node. Последните три реда са важни.
Когато test.js се анализира първоначално, функциите sayHi и add се обработват само предварително, което ускорява анализирането на изходния код.
След това, когато извикаме sayHi, функцията се анализира напълно.
Важно: add никога не се анализира напълно. Това спестява време за анализатор и намалява консумацията на V8 памет.
Какъв е проблемът с мързеливия анализ?
Нека премахнем неизползваната функция за добавяне от кода по-долу.
Резултат от d8 --trace_parse test.js:
V8 първо обработва sayHi, последвано от пълен анализ. Тогава не е необходим предварителен анализ и нашата програма работи по-бързо, без да се опитва да оптимизира V8!
V8 всъщност има евристика, при която функциите, затворени в скоби, винаги се анализират напълно. Например, това се отнася за незабавно извикани функционални изрази (IIFE):
Обърнете внимание, че тук няма функция за анализ: константи.
Сега да кажем, че искаме нашия пример sayHi да работи по-бързо. Какво можем да направим?
V8 все още подготвя sayHi, но можем да предотвратим това, като поставим израза на функцията в скоби.
Въпреки че не е IIFE, V8 ще използва евристика и ще направи пълен анализ от самото начало:
Оптимизиране-JS
Вместо ръчно да правим тези оптимизации и да правим нашия код труден за четене, можем да използваме инструмента optimize-js.
На практика често срещана причина за ненужен предварителен анализ е, че минификаторътUglifyJS премахва скобите от IIFE, за да спести байтове:
Това не променя поведението на кода, но нарушава евристиката на Chrome.
Ако стартирате optimize-js върху минимизирания код по-горе, скобите ще бъдат възстановени:
Тези оптимизации заслужават ли си усилията?
Документацията за optimize-js има резултати от няколко теста за производителност, показващи впечатляващо ускоряване от около 20%. В Chrome на моя лаптоп обаче действителното подобрение за примерното приложение React е само 6 ms (времето за зареждане на кода е намалено от 24 ms на 18 ms).
Вероятно има по-добри неща, които можете да направите, за да подобрите ефективността на вашите сайтове. Но ако ви свършат идеите, струва си да опитате optimize-js и да измерите дали има някакво значимо подобрение.