Оптимизиране на производителността на JavaScript за V8
Предговор
Най-важният съвет
Важно е да давате съвети за ефективност в контекста. Оптимизирането често се превръща в натрапчив навик и гмуркането дълбоко в дивата природа всъщност може да отвлече вниманието от по-важни неща. Имате нужда от цялостен поглед върху производителността на уеб приложенията – преди да се съсредоточите върху тези съвети за оптимизация, трябва да анализирате кода си с инструменти като PageSpeed и първо да постигнете добър общ резултат. Това ще помогне да се избегне преждевременната оптимизация.
Най-добрата стратегия, водеща до бързо уеб приложение, изглежда така:
За да се придържате към тази стратегия е важно да разберете как V8 оптимизира JS, да разберете как се случват нещата по време на изпълнение. Също така е важно да имате правилните инструменти. В своята реч Даниел посвети повече време на инструментите за разработчици; в тази статия разглеждам главно характеристиките на архитектурата V8.
Скрити класове
Докато свойството ".z" не беше добавено към p2, p1 и p2 имаха един и същ скрит клас вътре в компилатора и V8 можеше да използва един и същ оптимизиран собствен код и за двата обекта. Колкото по-рядко променяте скрития клас, толкова по-добра ще бъде производителността.
- Инициализирайте всички обекти в конструкторите, така че да се променят възможно най-малко по-късно.
- Винаги инициализирайте свойствата на обекта в същия ред.
V8 следи как използвате променливи и използва най-ефективното представяне за всеки тип. Промяната на типа може да бъде доста скъпа, така че се опитайте да не смесвате числа с плаваща запетая и цели числа. По принцип е по-добре да използвате цели числа.
- Опитайте се да използвате 31-битови цели числа със знак навсякъдевъзможно е.
V8 използва два вида вътрешно представяне на масиви:
- Истински масиви за компактни последователни ключове.
- Хеш таблици иначе.
Масивите от числа с двойна точност работят най-бързо - стойностите в тях се разопаковат и съхраняват като елементарни типове, а не като обекти. Безразсъдното използване на масиви може да доведе до често разопаковане-опаковане:
Така ще стане много по-бързо:
В първия пример отделните присвоявания се извършват последователно и в момента, в който a[2] получи стойност, компилаторът преобразува a в масив от неопаковани двойници и когато a[3] се инициализира към нечислов елемент, се извършва обратното преобразуване. Във втория пример компилаторът веднага ще избере необходимия тип масив.
- Малките фиксирани масиви се инициализират най-добре с помощта на литерал на масив.
- Попълване на малки масиви ( add() прави кода полиморфен:
Оптимизиращ компилатор
Паралелно с работата на базовия компилатор, оптимизиращият компилатор прекомпилира "горещи", т.е. тези, които се изпълняват често, кодови секции. Той използва информация за типа, съхранявана във вградени кешове.
Оптимизиращ компилатор се опитва да вгради функции в сайтовете за повикване, което ускорява изпълнението (но увеличава консумацията на памет) и позволява допълнителни оптимизации. Мономорфните функции и конструктори могат лесно да бъдат изцяло вградени, което е още една причина да се стремим да ги използваме.
Можете да видите какво точно се оптимизира във вашия код, като използвате самостоятелната версия на двигателя d8:
(имената на оптимизираните функции ще бъдат отпечатани в stdout )
Не всичкифункциите могат да бъдат оптимизирани. По-специално, оптимизиращият компилатор пропуска всички функции, съдържащи блокове try/catch.
Ако трябва да използвате блок try/catch, поставете критичен за производителността код отвън. Пример:
Може би в бъдеще ситуацията ще се промени и ще можем да компилираме блокове try/catch с оптимизиращ компилатор. Можете да видите кои функции се игнорират, като посочите опцията --trace-bailout при стартиране на d8:
Деоптимизация
Кодът, генериран от оптимизиращ компилатор, не винаги е по-бърз. В този случай се използва оригиналната, неоптимизирана версия. Лошо оптимизираният код се отхвърля и изпълнението продължава от подходящото място в кода, генериран от базовия компилатор. Може би този код ще бъде оптимизиран отново скоро, ако обстоятелствата позволяват. По-специално, промяната на скрити класове във вече оптимизиран код води до деоптимизация.
- Избягвайте промяна на скрити класове в оптимизирани функции.
Можете да видите кои функции са деоптимизирани, като стартирате d8 с опцията --trace-deopt:
Други V8 инструменти
Изброените по-горе функции могат да бъдат предадени на Google Chrome при стартиране:
d8 също има профайлър:
Профилът за вземане на проби d8 прави моментни снимки на всяка милисекунда и записва във v8.log.
Важно е да разберете как работи двигателят V8, за да напишете добре оптимизиран код. И не забравяйте за общите принципи, описани в началото на статията: