предисловие
JavaScript, безусловно, является одним из самых популярных языков программирования, и у него всегда была большая пользовательская база, а с использованием сервера (NodeJs) он приобрел большую жизнеспособность. Языки программирования делятся на два типа: компилируемые языки и интерпретируемые языки.Компилируемые языки должны быть полностью скомпилированы перед выполнением, тогда как интерпретируемые языки компилируются и выполняются одновременно.Очевидно, что скорость выполнения интерпретируемых языков медленнее, чем у скомпилированных языков, а JavaScript — это интерпретируемый язык сценариев, который поддерживает динамическую типизацию, слабую типизацию, языки на основе прототипов и встроенную поддержку типов. Поскольку JavaScript выполняется на внешнем интерфейсе и должен своевременно реагировать на запросы пользователей, для этого требуется, чтобы JavaScript анализировался и выполнялся быстро.
С развитием веб-технологий JavaScript должен выполнять все больше и больше работы, которая уже давно превзошла объем «проверки формы», требующей быстрого анализа и выполнения скриптов JavaScript. Движок V8 был создан для решения этой проблемы, и он также используется в узле для разбора JavaScript.
Как V8 значительно повышает производительность JavaScript? Изучив некоторые книги и статьи, я разобрался с соответствующим содержанием V8.Эта статья познакомит вас с V8. (Эта статья была опубликована во внутренней сети компании в начале 2017 года, и реакция была хорошей. Недавно я реорганизовал ее в первую публикацию Zhihu, надеясь помочь большему количеству людей понять двигатель V8.Воспроизведение требует моего согласия)
1. Движок рендеринга и рендеринг веб-страницы
С момента своего появления в конце 1980-х и начале 1990-х годов браузеры прошли долгий путь, и их функции становились все более и более богатыми, включая работу в сети, управление ресурсами, просмотр веб-страниц, управление несколькими страницами, плагины и расширения, управление закладками. , история Управление записями, управление настройками, управление загрузками, учетные записи и синхронизация, механизмы безопасности, управление конфиденциальностью, темы внешнего вида, инструменты разработчика и многое другое. Среди этих функций предоставление пользователям услуг просмотра веб-страниц, несомненно, является наиболее важной функцией, и соответствующее содержание будет представлено ниже.
1.1. Движок рендеринга
Механизм рендеринга: способен преобразовывать текст HTML/CSS/JavaScript и соответствующие файлы ресурсов в результаты изображения. Основная роль механизма рендеринга заключается в преобразовании файлов ресурсов в видимые пользователю результаты. В процессе разработки браузеров разные производители разработали разные механизмы рендеринга, такие как Tridend (IE), Gecko (FF), WebKit (браузер Safari, Chrome, Andriod) и так далее. WebKit — это проект с открытым исходным кодом, инициированный Apple в 2005 году, который привлек внимание многих компаний. Более того, была разработана веб-операционная система на основе WebKit, поддерживающая HTML5 (например, Chrome). ОС, веб-ОС).
Ниже приведена общая структура WebKit:
Модули, обведенные сплошной линией на приведенном выше рисунке, являются общими частями всех трансплантатов, и разные производители, обведенные пунктирной линией, могут реализовать их самостоятельно. Ниже приводится введение:
- Операционная система: Это компьютерная программа, которая управляет аппаратными и программными ресурсами компьютера и контролирует их. Это самое основное системное программное обеспечение, которое работает непосредственно на «голом железе». Любое другое программное обеспечение может работать только при поддержке операционной системы. WebKit также работает в операционных системах.
- Сторонние библиотеки, обеспечивающие поддержку WebKit, такие как графическая библиотека, сетевая библиотека, видеотека и т. д.
- WebCore — это общая часть, используемая различными браузерами, включая синтаксические анализаторы HTML, синтаксические анализаторы CSS, DOM и SVG и т. д. JavaScriptCore, движок по умолчанию для WebKit, был заменен движком V8 в линейке Google. Порты WebKit – это неразделяемая часть WebKit. Из-за различий в платформах, сторонних библиотек и разных требований различные трансплантации приводят к непоследовательному поведению разных версий WebKit. Это ключевая часть различий в производительности и функциях разных браузеров. .
- Встроенный программный интерфейс WebKit, который вызывается браузерами, тесно связан с трансплантацией, и разные трансплантации имеют разные спецификации интерфейса.
- Тестовые наборы, в том числе тестовые наборы компоновки и тестовые наборы производительности, используются для проверки правильности результатов рендеринга.
1.2. Процесс рендеринга веб-страницы
Различные модули движка рендеринга представлены выше, так какой процесс должна пройти веб-страница, чтобы попасть к пользователю?
Во-первых, это содержимое веб-страницы, которое вводится в анализатор HTML, анализатор HTML анализирует его, а затем строит дерево DOM.В течение этого периода, если встречается код JavaScript, он передается движку JavaScript для обработки; если информация о стиле поступает из синтаксического анализатора CSS, создается внутренняя модель рисования. В этой модели модуль компоновки вычисляет информацию о положении и размере каждого элемента внутри модели, и, наконец, модуль рисования завершает рисование из модели в изображение. Процесс рендеринга веб-страницы можно условно разделить на следующие три этапа.
1.2.1. От входного URL до создания DOM-дерева
- Введите URL-адрес в адресную строку, и WebKit вызовет загрузчик ресурсов для загрузки соответствующего ресурса;
- Загрузчики полагаются на сетевые модули для установления соединений, отправки запросов и получения ответов;
- WebKit получает различные веб-страницы или данные ресурсов, некоторые из которых могут быть получены синхронно или асинхронно;
- Веб-страница преобразуется в слова парсером HTML;
- Интерпретатор строит узлы на основе слов для формирования дерева DOM;
- Если узел представляет собой код JavaScript, вызовите механизм JavaScript для интерпретации и выполнения;
- Код JavaScript может изменять древовидную структуру DOM;
- Если узел зависит от других ресурсов, таких как изображения\css, видео и т. д., вызовите загрузчик ресурсов для их загрузки, но они загружаются асинхронно и не препятствуют дальнейшему созданию текущего дерева DOM; если это URL-адрес ресурса JavaScript (без пометки асинхронного метода), затем необходимо остановить создание текущего дерева DOM до тех пор, пока JavaScript не будет загружен и выполнен движком JavaScript, прежде чем продолжить создание дерева DOM.
1.2.2 От дерева DOM к построению контекста рисования WebKit
- Файлы CSS интерпретируются интерпретатором CSS как внутренние представления;
- После того, как интерпретатор CSS завершит свою работу, он добавляет информацию о стиле к дереву DOM для создания дерева RenderObject;
- Когда создается узел RenderObject, WebKit строит дерево RenderLayer на основе иерархии веб-страницы и строит виртуальный контекст рисования.
1.2.3 Отрисовка контекста для окончательного рендеринга изображения
- Контекст рисования — это независимый от платформы абстрактный класс, который связывает каждую операцию рисования с другим конкретным классом реализации, то есть с конкретным классом реализации рисования;
- Класс реализации чертежа также может иметь простые реализации или сложные реализации, такие как программная визуализация, аппаратная визуализация, синтетическая визуализация и т. д.;
- Класс реализации рисования сохраняет результаты рисования библиотеки 2D-графики или библиотеки 3D-графики и передает их в интерфейс браузера для отображения.
Выше приведен полный процесс рендеринга. Многие современные веб-страницы являются динамическими. При взаимодействии между веб-страницей и пользователем браузеру необходимо постоянно повторять процесс рендеринга.
1.3. Движок JavaScript
JavaScript по сути является интерпретируемым языком. В отличие от скомпилированных языков, он должен выполняться и анализироваться одновременно. Компилируемые языки компилируются во время выполнения и могут выполняться напрямую с более высокой скоростью выполнения (как показано на рисунке выше). . ). Код JavaScript анализируется и выполняется на стороне браузера, и если это займет слишком много времени, это повлияет на работу пользователя. Поэтому повышение скорости парсинга JavaScript является главным приоритетом. Связь между движком JavaScript и движком рендеринга показана на следующем рисунке:
Язык JavaScript является интерпретируемым языком.Для повышения производительности в виртуальной машине Java и компиляторе C++ введены многие технологии. Теперь процесс выполнения движка JavaScript выглядит примерно так:
Исходный код-→абстрактное синтаксическое дерево-→байт-код-→JIT-→собственный код (двигатель V8 не имеет промежуточного байт-кода). Пример абстрактного синтаксического дерева для фрагмента кода выглядит следующим образом:
function demo(name) {
console.log(name);
}
Абстрактное синтаксическое дерево выглядит следующим образом:
V8 более прямо преобразует абстрактное синтаксическое дерево в локальный код с помощью технологии JIT, отказываясь от некоторых оптимизаций производительности, которые можно выполнить на этапе байт-кода, но обеспечивая скорость выполнения. После того, как V8 сгенерирует собственный код, он также собирает некоторую информацию с помощью Profiler для оптимизации собственного кода. Хоть и меньше оптимизация производительности на этапе генерации байткода, зато сильно сокращается время конвертации.
Но в конце апреля 2017 года была выпущена версия 5.9 v8, в которую был добавлен интерпретатор байт-кода Ignition, который будет запускаться по умолчанию и с этого момента будет иметь примерно тот же процесс, что и в JCore. Причины этого изменения: (основная мотивация) уменьшить объем памяти, занимаемый машинным кодом, то есть пожертвовать временем ради места; повысить скорость запуска кода; провести рефакторинг кода v8 для уменьшения кода v8. сложность (V8 Ignition: неразрывная связь между движком JS и байт-кодом - Техническое сообщество CNode).
Производительность JavaScript по-прежнему далека от C, и обозримое будущее можно только оценить как близкое к нему, а не сравнивать с ним, что определяется типом языка. Двигатель V8 будет представлен более подробно ниже.
2. Двигатель V8
Движок V8 — это реализация движка JavaScript, первоначально разработанная некоторыми языковыми экспертами, а затем приобретенная Google, исходный код которого был затем открыт Google. V8, разработанный с использованием C ++, перед запуском JavaScript, по сравнению с другим движком JavaScript, преобразованным или интерпретированным байт-кодом, V8 скомпилирован в собственный машинный код (процессоры IA-32, x86-64, ARM или MIPS) и используется в качестве встроенного кеша (встроенное кэширование) или тому подобное для повышения производительности. Благодаря этим функциям программы на JavaScript работают так же быстро, как бинарные программы под движком V8. V8 поддерживает многие операционные системы, такие как Windows, Linux, Android и т. д., а также другие аппаратные архитектуры, такие как IA32, X64, ARM и т. д., с хорошей переносимостью и кросс-платформенными функциями.
Структура кода проекта V8 выглядит следующим образом:
2.1 Представление данных
JavaScript — это язык с динамической типизацией. Тип переменных не может быть точно известен во время компиляции, а может быть определен только во время выполнения. В отличие от языков со статической типизацией, таких как C++ или Java, тип переменных может быть известен точно во время компиляции. . Однако вычисление и определение типов во время выполнения может серьезно повлиять на производительность языка, что является одной из причин, по которой JavaScript намного менее эффективен, чем C++ или JAVA.
В C++ исходный код необходимо скомпилировать, прежде чем его можно будет выполнить.В процессе генерации нативного кода были определены адрес и тип переменной.При запуске нативного кода адрес переменной и метода может быть доступ с помощью массива и смещения.Дополнительный поиск может быть выполнен в нескольких машинных инструкциях, что экономит время при определении типов и адресов. Поскольку JavaScript является нетипизированным языком, он не может знать тип и адрес переменных во время выполнения, как C++, и его необходимо временно определить. JavaScript и C++ имеют следующие отличия:
- Компилировать для определения положения, этап компиляции C++ определяет информацию о смещении позиции, доступ к которой осуществляется напрямую во время выполнения, а JavaScript определяется на этапе выполнения, а свойства объекта могут быть изменены во время выполнения;
- Совместное использование информации о смещении, C++ имеет определения типов, которые не могут быть динамически изменены во время выполнения, и может совместно использовать информацию о смещении.Каждый объект в JavaScript является самоописываемым, а атрибуты и информация о смещении позиции включены в его собственную структуру;
- Поиск информации о смещении, поиск адреса смещения C++ очень прост. На этапе кода компиляции позиция смещения напрямую устанавливается для определенного типа используемой переменной-члена. Когда объект используется в JavaScript, он должен соответствовать имени атрибута, чтобы найти соответствующее значение много операций.
В процессе выполнения кода очень распространен и частый доступ к переменным, доступ к которому можно получить через смещения и выполнить с помощью нескольких ассемблерных инструкций.Если имя атрибута совпадает, требуется больше ассемблерных инструкций и больше места в памяти. Пример выглядит следующим образом:
В JavaScript, за исключением пяти простых переменных: boolean, number, string, null и undefined, другие данные являются объектами.V8 использует особый способ их представления, тем самым оптимизируя внутреннее представление JavaScript.
В V8 внутреннее представление данных состоит из фактического содержимого данных и дескриптора данных. Фактическое содержимое данных имеет переменную длину и другой тип; дескриптор имеет фиксированный размер и содержит указатель на данные. Этот дизайн может облегчить V8 сборку мусора и перемещение содержимого данных.Если указатель используется напрямую, возникнут проблемы или дополнительные накладные расходы.Если используется дескриптор, необходимо только изменить указатель в дескрипторе, а пользователь по-прежнему использует дескриптор. , изменения указателя прозрачны для пользователя.
За исключением нескольких данных (например, целочисленных данных), хранимых самим дескриптором, другое содержимое ограничено размером дескриптора и переменной длиной и хранится в куче. Integer получает значение непосредственно из значения, а затем использует указатель, чтобы указать на него, что может уменьшить использование памяти и повысить скорость доступа. Размер объекта дескриптора составляет 4 байта (32-разрядные устройства) или 8 байт (64-разрядные устройства), а в JavaScriptCore для представления дескриптора используется 8 байтов. Все объекты, хранящиеся в куче, выровнены по 4 байтам, поэтому последние два бита их указателей не нужны.V8 использует эти два бита для представления типа данных: 00 для целых чисел и 01 для остальных.
Реализация объектов JavaScript в V8 состоит из трех частей: скрытый указатель класса, который представляет собой скрытый класс, созданный v8 для объектов JavaScript; указатель таблицы значений свойств, указывающий на значения свойств, содержащиеся в объекте; элемент указатель таблицы, указывающий на свойства, содержащиеся в объекте.
2.2 Рабочий процесс
Как упоминалось ранее, движок V8 в основном имеет два этапа в процессе выполнения JavaScript: компиляцию и выполнение.В отличие от полной компиляции C++ перед выполнением, JavaScript необходимо компилировать и выполнять, когда пользователь его использует. В версии 8 код, связанный с JavaScript, не компилируется сразу весь, а только тогда, когда необходимо выполнить какой-то код, что улучшает время отклика и снижает затраты времени. В движке V8 исходный код сначала преобразуется синтаксическим анализатором в абстрактное синтаксическое дерево (AST), а затем собственный исполняемый код генерируется непосредственно из AST с помощью генератора полного кода JIT-компилятора. Этот процесс отличается от JAVA, который сначала генерирует байт-код или промежуточное представление, что сокращает время преобразования из AST в байт-код и повышает скорость выполнения кода. Но отсутствие промежуточного процесса преобразования в байт-код снижает возможность оптимизации кода.
Основные классы, используемые движком V8 при компиляции машинного кода, следующие:
- Сценарий: представляет код JavaScript, включая исходный код и локальный код, сгенерированный после компиляции, то есть запись компиляции и текущую запись;
- Компилятор: класс компилятора, класс вспомогательной группы Script для компиляции и генерации кода, вызов интерпретатора (парсера) для генерации AST и генератора полного кода, а также преобразования AST в собственный код;
- AstNode: класс узлов абстрактного синтаксического дерева, который является базовым классом всех остальных узлов, содержит множество подклассов, и позже для разных подклассов будут сгенерированы разные локальные коды;
- AstVisitor: класс посетителя абстрактного синтаксического дерева, который в основном используется для обхода разнородных абстрактных синтаксических деревьев;
- FullCodeGenerator: подкласс класса AstVisitor, который генерирует собственный исполняемый код для JavaScript путем обхода AST.
Процесс компиляции кода JavaScript примерно таков: класс Script вызывает функцию Compile класса Compiler, чтобы сгенерировать для него собственный код. Функция Compile сначала использует класс Parser для создания AST, а затем использует класс FullCodeGenerator для создания машинного кода. Собственный код тесно связан с конкретной аппаратной платформой, и FullCodeGenerator использует несколько серверных частей для создания собственного кода сборки, соответствующего платформе. Поскольку FullCodeGenerator генерирует соответствующий ассемблерный код для каждого узла путем обхода AST, глобальное представление отсутствует, и оптимизация между узлами невозможна.
Перед компиляцией V8 создает ряд глобальных объектов и загружает некоторые встроенные библиотеки (например, математическую библиотеку) для создания среды выполнения. Более того, в исходном коде JavaScript не все функции компилируются для генерации нативного кода, а компилируются лениво и будут компилироваться только при вызове.
Поскольку в V8 отсутствует ссылка для генерации промежуточного кода и отсутствуют необходимые оптимизации, для повышения производительности V8 будет использовать профилировщик данных для сбора некоторой информации после генерации локального кода, а затем оптимизировать локальный код в соответствии с этими данными. код, процесс постепенного улучшения. В то же время, когда обнаружится, что производительность оптимизированного кода не так хороша, как у неоптимизированного кода, V8 вернет исходный код, то есть оптимизированный откат. Этап выполнения описан ниже.Основные классы, используемые на этом этапе, следующие:
- Сценарий: представляет код JavaScript, включая исходный код и локальный код, сгенерированный после компиляции, то есть запись компиляции и текущую запись;
- Выполнение: Вспомогательный групповой класс для выполнения кода, включая некоторые важные функции, такие как функция вызова, какая вспомогательная группа вводит и выполняет код сценария;
- JSFunction: класс представления функции JavaScript, который необходимо выполнить;
- Время выполнения: Вспомогательные групповые классы, которые запускают эти собственные коды, в основном предоставляя вспомогательные групповые функции, необходимые во время выполнения, такие как: доступ к атрибутам, преобразование типов, компиляция, арифметика, битовые операции, сравнение, регулярное выражение и т. д.;
- Heap: класс кучи памяти, необходимый для запуска машинного кода;
- MarkCompactCollector: основной класс реализации механизма сборки мусора, который используется для основных процессов сборки мусора, таких как маркировка, очистка и сортировка;
- SweeperThread: поток, отвечающий за сборку мусора.
Сначала скомпилируйте и сгенерируйте этот собственный код по мере необходимости, то есть используйте эти классы и операции на этапе компиляции. В V8 функция является базовой единицей.При вызове функции JavaScript V8 выясняет, сгенерировала ли функция нативный код, и если да, то вызывает функцию напрямую. В противном случае механизм V8 генерирует собственный код, принадлежащий функции. Это экономит время и сокращает время, затрачиваемое на работу с неиспользуемым кодом. Во-вторых, выполнить скомпилированный код для создания объектов JS для JavaScript, что требует, чтобы класс Runtime помогал в создании объектов и выделении памяти из класса Heap. Опять же, с помощью вспомогательных групповых функций в классе Runtime можно выполнить некоторые функции, например, доступ к атрибутам. Наконец, неиспользуемое пространство помечается, очищается и удаляется сборщиком мусора.
2.3 Оптимизированный откат
Поскольку V8 напрямую генерирует собственный код на основе AST и не был оптимизирован промежуточным уровнем представления, собственный код не был оптимизирован должным образом. Поэтому в 2010 году V8 представила новый компилятор-Crankshaft, который в основном оптимизирует функции hotspot, запускает анализ на основе исходного кода JavaScript вместо нативного кода, а также строит график Hydroger и выполняет анализ оптимизации на его основе.
Компиляторы Crankshaft обычно делают оптимистичные и смелые прогнозы из соображений производительности — код стабилен, а типы переменных не меняются, поэтому может быть сгенерирован эффективный собственный код. Однако, учитывая, что JavaScript — язык со слабой типизацией, типы переменных также могут изменяться во время выполнения, поэтому V8 откатывает оптимизацию, которую компилятор считает само собой разумеющейся, что называется откатом оптимизации.
Пример выглядит следующим образом:
var counter = 0;
function test(x, y) {
counter++;
if (counter < 1000000) {
// do something
return 'jeri';
}
var unknown = new Date();
console.log(unknown);
}
После многократного вызова функции движок V8 может запустить компилятор Crankshaft для ее оптимизации, и оптимизирующий код считает, что информация о типе примера кода определена. Однако, поскольку новая Date() фактически не выполнялась и тип неизвестной переменной не был получен, V8 должен выполнить откат этой части кода. Оптимизация отката — трудоемкая операция, старайтесь не запускать оптимизацию этой операции в процессе написания кода.
В недавно выпущенной версии V8 5.9 был добавлен интерпретатор байт-кода Ignition, а TurboFan и Ignition объединены для завершения компиляции JavaScript. В этом выпуске удален старый компилятор Cranshaft, а новый Turbofan позволяет оптимизировать код непосредственно из байт-кода и деоптимизировать непосредственно в байт-код, когда требуется деоптимизация, без необходимости рассматривать исходный код JS.
2.4 Скрытые классы и встроенные кэши
2.4.1 Скрытые классы
При выполнении кода C++ переменная информация может быть получена на основе информации о смещении с помощью всего нескольких инструкций, в то время как в JavaScript значения атрибутов необходимо найти путем сопоставления строк, что требует большего количества операций для доступа к переменной информации, в то время как доступ к переменной кода встречается очень часто, что также ограничивает производительность JavaScript. V8 заимствует идею класса и позиции смещения, улучшает исходный метод доступа к значению атрибута через сопоставление имени атрибута и использует для достижения этого механизм позиции смещения, аналогичный компилятору C++, который является скрытым классом.
Скрытый класс делит объекты на разные группы.Если объекты в группе имеют одинаковое имя атрибута и значение атрибута, имена атрибутов и соответствующие позиции смещения этих групп хранятся в скрытом классе, и все объекты в группе разделяют эту информацию. . В то же время могут быть идентифицированы объекты с различными свойствами. Пример выглядит следующим образом:
Два объекта p и q построены с использованием Point, эти два объекта имеют одинаковое имя свойства, V8 объединяет их в одну группу, то есть в скрытый класс, эти свойства имеют одинаковое значение смещения в скрытом классе, p и q разделяют эту информацию, и при выполнении доступа к свойству она должна основываться только на значении смещения скрытого класса. Поскольку JavaScript является динамически типизированным языком, тип переменных может быть изменен во время выполнения.Если qz=2 выполняется после выполнения приведенного выше кода, то p и q больше не будут считаться группой, а q будет новым скрытый класс.
2.4.2. Встроенный кэш
Обычный процесс доступа к свойствам объекта: сначала получить адрес скрытого класса, затем найти значение смещения в соответствии с именем свойства, а затем вычислить адрес свойства. Хотя объем работы по поиску во всей среде выполнения значительно сократился по сравнению с прошлым, он по-прежнему занимает много времени. Можно ли кэшировать результаты предыдущего запроса для повторного доступа? Конечно можно, это встроенный кеш.
Общая идея встроенного кеша состоит в том, чтобы сохранить скрытый класс и значение смещения начального поиска.При выполнении следующего поиска сначала сравнить, является ли текущий объект предыдущим скрытым классом.Если да, то использовать предыдущий результат кеша непосредственно, чтобы уменьшить количество времени для поиска в таблице. Конечно, если у объекта несколько атрибутов, вероятность ошибок кеша возрастет, так как после изменения типа определенного атрибута изменится и скрытый класс объекта, который не согласуется с предыдущим кешем и нуждается в поиске предыдущим способом хеш-таблица.
2.5 Управление памятью
При использовании памяти через JavaScript в Node вы обнаружите, что можно использовать только часть памяти (около 1,4 ГБ в 64-разрядной системе и около 0,7 ГБ в 32-разрядной системе), основной причиной является ограничение мусора V8. механизм сбора (при наличии). Если используемая память слишком велика, V8 будет потреблять больше ресурсов и времени при сборке мусора, что серьезно повлияет на эффективность выполнения JS). Управление памятью описано ниже.
Группа управления памятью состоит из двух частей: выделение и утилизация. Разделение памяти V8 выглядит следующим образом:
- Зона: Управление небольшими блоками памяти. Сначала он обращается за частью памяти сам по себе, а затем управляет и выделяет небольшую часть памяти.Когда выделяется небольшая часть памяти, она не может быть освобождена Zone, и может восстановить только всю небольшую память, выделенную Zone за один раз. Когда процессу требуется много памяти, Zone нужно будет выделять много памяти, но она не может быть вовремя переработана, что приведет к нехватке памяти.
- Куча: управляет данными, используемыми JavaScript, сгенерированным кодом, хеш-таблицами и т. д. Для облегчения сборки мусора куча делится на три части:
Молодое поколение: выделяет место в памяти для вновь созданных объектов, часто требующих сборки мусора. Чтобы облегчить переработку контента в молодом поколении, молодое поколение можно разделить на две половины, одна половина используется для распределения, а другая половина отвечает за копирование объектов, которые необходимо сохранить ранее при переработке.
Устаревшая генерация: старые объекты, указатели, коды и другие данные сохраняются по мере необходимости, а сборка мусора выполняется реже.
Большие объекты: выделение памяти для тех объектов, которым требуется больше памяти.Конечно, это может также включать память, выделенную для данных и кода.Для страницы выделяется только один объект.
вывоз мусора
V8 использует выделение памяти для генерации и больших данных.При высвобождении памяти он использует упрощенный алгоритм сортировки, чтобы пометить объекты, на которые нет ссылок, затем удалить немаркированные объекты и, наконец, отсортировать и сжать эти несохраненные объекты для завершения сборки мусора.
В V8 больше используется молодое поколение и старое поколение. Сборка мусора объектов в молодом поколении в основном выполняется алгоритмом Scavenge. В конкретной реализации Scavenge в основном используется алгоритм Чейни: алгоритм сборки мусора, реализованный посредством репликации. Он делит память кучи на два полупространства: одно используется (из пространства), а другое простаивает (в пространство). Когда объект размещается, он сначала размещается в пространстве From. Когда начнется сборка мусора, будут проверены уцелевшие объекты в пространстве From, эти уцелевшие объекты будут скопированы в пространство To, а пространство, занятое невыжившими объектами, будет освобождено. После завершения копирования роли пространств From и To меняются местами. В процессе сборки мусора это размещение уцелевших объектов в двух Копировать между полупространствами. Объекты в молодом поколении имеют возможность быть повышены до старого поколения.Существуют два основных условия: первое — объект подвергся восстановлению Scavenge, а второе — коэффициент использования памяти в пространстве To превышает лимит.
Для объектов в старом поколении, поскольку большая доля приходится на уцелевшие объекты, с описанным выше методом возникнут две проблемы: первая состоит в том, что уцелевших объектов много, и эффективность копирования уцелевших объектов будет очень низкой; другая проблема по-прежнему Проблема потери половины пространства. С этой целью V8 в основном использует комбинацию Mark-Sweep (удаление меток) и Mark-Compact (завершение меток) для сборки мусора в старом поколении.
2.6 Снимки
Когда запускается движок V8, необходимо создать среду выполнения JavaScript, загрузить множество встроенных объектов и создать встроенные функции, такие как Array, String, Math и т. д. Чтобы сделать V8 чище, такие задачи, как загрузка объектов и построение функций, реализуются с использованием файлов JavaScript, а механизм V8 отвечает за предоставление механизма для поддержки этих файлов перед компиляцией и выполнением JavaScript.
Механизм V8 должен компилировать и выполнять эти встроенные коды JavaScript, используя кучу и т. д. для хранения объектов, кода и т. д., созданных во время выполнения, что требует времени. С этой целью в V8 был представлен механизм моментальных снимков. Сохраните и сериализуйте память после загрузки этих встроенных объектов и функций. Сериализованный результат легко десериализовать, а время запуска с помощью механизма моментальных снимков можно сократить на несколько миллисекунд. Механизм моментальных снимков также может сериализовать некоторые файлы JavaScript, которые, по мнению разработчиков, необходимы для сокращения времени обработки. Однако загруженный код механизма моментальных снимков не может быть оптимизирован таким компилятором, как CrankShaft, и могут возникнуть проблемы с производительностью.
3.V8 VS JavaScriptCore
Механизм JavaScriptCore — это механизм JavaScript по умолчанию в WebKit и широко используемый проект Apple с открытым исходным кодом. Изначально производительность была не очень хорошей, с 2008 года был проведен ряд оптимизаций, перереализованы компилятор и интерпретатор байт-кода, что значительно улучшило производительность движка. Впоследствии были представлены такие технологии, как встроенный кэш, JIT на основе регулярных выражений, простой JIT и интерпретатор байт-кода, а движок JavaScriptCore также постоянно совершенствуется и развивается.
Со дня своего рождения движок V8 был нацелен на оптимизацию производительности и внедрил множество новых технологий, что в значительной степени способствовало быстрому развитию производительности движка JavaScript во всей отрасли. В целом, движок V8 более агрессивен, отдавая предпочтение новым технологиям, которые могут повысить производительность, в то время как движок JavaScriptCore более надежен и постепенно меняет свою производительность. Общий рабочий процесс движка JavaScript (включая v8 и JavaScriptCore) выглядит следующим образом:
Общий процесс JavaScriptCore: исходный код → абстрактное синтаксическое дерево → байт-код → JIT → собственный код. Между JavaScriptCore и V8 есть некоторые различия, самая большая разница — это новыйбайт-кодПромежуточное представление и многоуровневые JIT-компиляторы (такие как: простой JIT-компилятор, DFG JIT-компилятор, LLVM и т. д.) добавляются для оптимизации производительности, а собственный код постоянно оптимизируется. (В версии 5.9 V8 был добавлен интерпретатор байт-кода Ignition.TurboFan и Ignition объединяются для завершения компиляции JavaScript.После этого V8 будет иметь примерно тот же процесс, что и JavaScriptCore.Версия V8 в Node 8.0 - 5.8)
Кроме того, с точки зрения представления данных, V8 использует представление данных, которое соответствует количеству машинных битов на разных машинах, в то время как дескрипторы в JavaScriptCore используют 64-битное представление, которое может представлять более широкий диапазон чисел, поэтому даже в 32-битной машине , типы с плавающей запятой также можно хранить в дескрипторе, больше не нужно обращаться к данным в куче, а также они занимают больше места.
4. Расширение функций
Основной функцией движка JavaScript является разбор и выполнение кода JavaScript, который часто не может удовлетворить разнообразные потребности пользователей, поэтому для улучшения его возможностей могут быть добавлены расширения. Двигатель V8 имеет два механизма расширения: связывание и расширение.
4.1. Механизм привязки
Создайте файлы привязки, используя файлы IDL или файлы интерфейса, и скомпилируйте эти файлы с помощью механизма V8. IDL используется в WebKit для определения JavaScript, но отличается от IDL некоторыми изменениями. Шаги для определения нового интерфейса примерно следующие:
- 1. Определите новый файл интерфейса, который можно вызывать в коде JavaScript, например, mymodule.MyObj.myAttr;
module mymodule {
interface [
InterfaceName = MyObject
] MyObj {
readonly attribute long myAttr;
DOMString myMethod (DOMString myArg);
};
}
- 2. Реализуйте класс интерфейса на основе стандартного интерфейса, определенного движком, и сгенерируйте файл привязки, требуемый движком JavaScript. WebKit предоставляет инструменты для создания необходимых классов привязки, которые различаются в зависимости от ядра и языка разработки ядра. Механизм V8 создаст два файла привязки, v8MyObj.h (конкретный код реализации класса MyObj) и V8MyObj.cpp (код моста, вспомогательная группа регистрирует функцию моста в механизме V8) для приведенного выше примера кода.
Механизм привязки движка JavaScript должен компилировать и упаковывать код расширения вместе с движком JavaScript и не может динамически внедрять эти собственные коды после запуска движка по мере необходимости. В реальной WEB-разработке все разработчики опираются на существующие браузеры, и вмешиваться в компиляцию движков JavaScript невозможно.Механизм привязки имеет большие ограничения, но он очень эффективен и подходит для высоких требований к производительности.
4.2 Механизм расширения
Расширение возможностей выполняется с помощью расширения базового класса V8 без компиляции с ядром V8, а функции могут динамически добавляться в ядро с большой гибкостью.
Механизм расширения является общей идеей, V8 предоставляет базовый класс для расширения и функцию глобальной регистрации, JavaScript для расширения возможностей необходимо выполнить следующие шаги:
class MYExtension : public v8::Extension {
public:
MYExtension() : v8::Extension("v8/My", "native function my();") {}
virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction (
v8::Handle<v8::String> name) {
// 可以根据name来返回不同的函数
return v8::FunctionTemplate::New(MYExtention::MY);
}
static v8::Handle<v8::Value> MY(const v8::Arguments& args) {
// Do sth here
return v8::Undefined();
}
};
MYExtension extension;
RegisterExtension(&extension);
- 1. Создайте подкласс на основе базового класса Extension и реализуйте его виртуальную функцию — GetNativeFunction, и решите вернуть реальную функцию в соответствии с именем параметра;
- 2. Создайте объект этого подкласса и зарегистрируйте объект в движке V8 с помощью функции регистрации, которую можно вызвать, когда JavaScript вызывает функцию «моя».
Механизм расширения заключается в вызове интерфейса V8 для внедрения новых функций.Динамическое расширение очень удобно, но не так эффективно, как механизм привязки.Подходит для сценариев с низкими требованиями к производительности.
Суммировать
В последние несколько лет JavaScript широко используется во многих областях, но из-за недостатков самого языка JavaScript эффективность выполнения невысока. Google также запустил некоторые веб-приложения JavaScript, такие как Gmail, Google Maps и офис Google Docs. На производительность этих приложений влияют не только сервер, сеть, механизм рендеринга и многие другие факторы, но и скорость выполнения самого JavaScript. Однако существующий движок JavaScript не может соответствовать новым требованиям, а низкая производительность всегда была самой большой проблемой разработчиков веб-приложений. Google начал исследование движка V8 и внедрил в движок JavaScript ряд новых технологий, которые значительно повысили эффективность выполнения JavaScript. Я считаю, что с непрерывным развитием движка V8 у JavaScript будет более широкий спектр сценариев приложений, и у фронтенд-инженеров также будет лучшее будущее! Затем в сочетании с приведенным выше введением в двигатель V8 мы должны обратить внимание на следующее в программировании:
- Типы. Для функций JavaScript — это язык с динамической типизацией. И JavaScriptCore, и V8 используют скрытые классы и встроенные кеши для повышения производительности. Чтобы обеспечить частоту попаданий в кеш, функция должна использовать меньше типов данных; для массивов старайтесь хранить один и тот же тип .данные, чтобы к ним можно было получить доступ по смещению.
- представление данных. Простые типы данных (например, целые числа) хранятся непосредственно в дескрипторе, что может сократить время адресации и использование памяти.Если они могут быть представлены целыми числами, старайтесь не использовать типы с плавающей запятой.
- ОЗУ. Хотя язык JavaScript будет выполнять сборку мусора сам, мы также должны попытаться вовремя освободить неиспользуемую память и установить null для объектов, которые больше не используются, или удалить их с помощью метода удаления (удаление с помощью метода удаления вызовет создание скрытых классов, которые необходимо обновить, много дополнительных операций).
- Оптимизация отката. После многократного выполнения не изменяйте оператор типа объекта, старайтесь не запускать откат оптимизации, иначе это сильно снизит производительность кода.
- новый механизм. Повысьте производительность, используя новые механизмы и новые интерфейсы, предоставляемые механизмами JavaScript или механизмами рендеринга.
использованная литература
- "Инсайдер технологии WebKit》
- "продвинутое программирование на JavaScript》
- «Введение в Node.js»
Почему двигатель V8 такой быстрый
V8 Ignition: неразрывная связь между движком JS и байт-кодом - Техническое сообщество CNode