V8 движок: о внутреннем устройстве V8 и оптимизации кода / Хабр
Содержание
V8 под капотом / Хабр
Ведущий разработчик Яндекс.Деньги Андрей Мелихов (также редактор/переводчик сообщества devSchacht) на примере движка V8 рассказывает о том, как и через какие стадии проходит программа, прежде чем превращается в машинный код, и зачем на самом деле нужен новый компилятор.
Материал подготовлен на основе доклада автора на конференции HolyJS 2017, которая проходила в Санкт-Петербурге 2-3 июня. Презентацию в pdf можно найти по этой ссылке.
Несколько месяцев назад вышел фильм «Последний убийца драконов». Там, если протагонист убивает дракона, то в мире исчезает магия. Я хочу сегодня выступить антагонистом, я хочу убить дракона, потому что в мире JavaScript нет места для магии. Все, что работает, работает явно. Мы должны разобраться, как оно устроено, чтобы понимать, как оно работает.
Я хочу поделиться с вами своей страстью. В какой-то момент времени я осознал, что плохо знаю, как устроен под капотом V8. Я начал читать литературу, смотреть доклады, которые в основном на английском, накопил знания, систематизировал и хочу их довести до вас.
Интерпретируемый или компилируемый у нас язык?
Я надеюсь, все знают отличие, но повторю. Компилируемые языки: в них исходный код преобразуется компилятором в машинный код и записывается в файл. В них используется компиляция до выполнения. В чем преимущество? Его не нужно повторно компилировать, он максимально автоматизирован для той системы, под которую скомпилирован. В чем недостаток? Если у вас меняется операционная система, и если у вас нет исходников, вы теряете программу.
Интерпретируемые языки – когда исходный код исполняется программой-интерпретатором. Преимущества в том, что легко достичь кроссплатформенности. Мы поставляем наш исходный код как есть, и если в этой системе есть интерпретатор, то код будет работать. Язык JavaScript, конечно, интерпретируемый.
Погрузимся в историю. В 2008 выходит браузер Chrome. В том году Google презентовал новый движок V8. В 2009 году на том же самом движке была представлена Node.js, которая состояла из V8 и библиотеки libUV, которая обеспечивает io, т.е. обращение к файлам, сетевые какие-то вещи и т.д. В общем, две очень важные вещи для нас построены на движке V8. Посмотрим, из чего он состоит.
В 2008 году движок был довольно прост внутри. Ну, относительно прост — схема его была простая. Исходный код попадал в парсер, из парсера — в компилятор, и на выходе мы получали полуоптимизированный код. Полуоптимизированный, потому что хорошей оптимизации тогда еще не было. Возможно, в те годы приходилось писать лучший JavaScript, потому что нельзя было надеяться на то, что оптимизатор его сам внутри заоптимизирует.
Для чего в этой схеме нужен парсер?
Парсер нужен для того, чтобы превращать исходный код в абстрактное синтаксическое дерево или AST. AST – такое дерево, в котором все вершины — операторы, а все листья — это операнды.
Посмотрим на примере математического выражения. У нас такое дерево, все вершины – операторы, ветви – операнды. Чем оно хорошо — тем, что из него очень легко генерировать позже машинный код. Кто работал с Assembler, знает, что чаще всего инструкция состоит из того, что сделать и с чем сделать.
И вот здесь как раз мы можем смотреть, являемся ли мы в текущей точке оператором или операндом. Если это оператор, смотрим его операнды и собираем команду.
Что в JavaScript происходит, если у нас есть, например, массив, и мы запрашиваем из него элемент по индексу 1? Появляется такое абстрактное синтаксическое дерево, у которого оператор «загрузить свойство по ключу», а операнды – это объект и ключ, по которому мы загружаем это свойство.
Зачем в JavaScript компилятор?
Как я сказал, язык у нас интерпретируемый, но в его схеме мы видим компилятор. Зачем он? На самом деле есть два типа компиляторов. Есть компиляторы (ahead-of-time), которые компилируют до выполнения, и компиляторы JIT, которые компилируют во время выполнения. И за счет JIT-компиляции получается хорошее ускорение. Для чего это нужно? Давайте сравним.
Есть один и тот же код. Один на Pascal, другой на JavaScript. Pascal — прекрасный язык. Я считаю, что с него и надо учиться программировать, но не с JavaScript. Если у вас есть человек, который хочет научиться программировать, то покажите ему Pascal или C.
В чем отличие? Pascal может быть и компилируемым и интерпретируемым, а JavaScript требует уже интерпретации. Самое важное отличие – это статическая типизация.
Потому что, когда мы пишем на Pascal, мы указываем переменные, которые необходимы, а потом пишем их типы. Потом компилятору легко построить хороший оптимизированный код. Как мы обращаемся к переменным в память? У нас есть адрес, и у нас есть сдвиг. Например, Integer 32, то мы делаем сдвиг на 32 по этому адресу в память и получаем данные.
В JavaScript нет, у нас типы всегда меняются во время выполнения, и компилятор, когда выполняет этот код, первый раз он его выполняет как есть, но собирает информацию о типах. И второй раз, когда он выполняет ту же самую функцию, он уже основываясь на данных, которые он получил в прошлый раз, предположив, какие были там типы, может сделать какую-то оптимизацию. Если с переменными все понятно, они определяются по значению, то что у нас с объектами?
Ведь у нас JavaScript, у него прототипная модель, и классов для объектов у нас нет. На самом деле есть, но они не видны. Это так называемые Hidden Classes. Они видны только самому компилятору.
Как создаются Hidden Classes?
У нас есть point – это конструктор, и создаются объекты. Сначала создается hidden class, который содержит только сам point.
Дальше у нас устанавливается свойство этого объекта x и из того, что у нас был hidden class, создаётся следующий hidden class, который содержит x.
Дальше у нас устанавливается y и, соответственно, мы получаем еще один hidden class, который содержит x и y.
Так мы получили три hidden class. После этого, когда мы создаем второй объект, используя тот же самый конструктор, происходит то же самое. Уже есть hidden classes, их уже не нужно создавать, с ними необходимо только сопоставить. Для того, чтобы позже мы знали, что эти два объекта одинаковы по структуре. И с ними можно похоже работать.
Но что происходит, когда мы позже еще добавляем свойство в объект p2? Создается новый hidden class, т.е. p1 и p2 уже не похожи. Почему это важно? Потому что, когда компилятор будет перебирать в цикле point, и вот у него будут все такие же, как p1, он их крутит, крутит, крутит, натыкается на p2, а у него другой hidden class, и компилятор уходит в деоптимизацию, потому что он получил не то, что ожидал.
Это так называемая утиная типизация. Что такое утиная типизация? Выражение появилось из американского сленга, если что-то ходит как утка, крякает как утка, то это утка. Т.е. если у нас p1 и p2 по структуре одинаковы, то они принадлежат к одному классу. Но стоит нам добавить в структуру p2 еще, и эти утки крякают по-разному, соответственно, это разные классы.
И вот мы получили данные о том, к каким классам относятся объекты, и получили данные о том, какого рода переменные, где эти данные использовать и как их хранить. Для этого используется система Inline Caches.
Рассмотрим, как происходит создание Inline Caches для этой части. Сначала, когда у нас код анализируется, он дополняется такими вызовами. Это только инициализация. Мы еще не знаем, какого типа будет у нас наш Inline Caches.
Мы можем сказать, что вот в этом месте проиницилизируй его, вот здесь загрузка this.primes:
Вот здесь загрузка по ключу:
А дальше операция BinaryOperation — это не значит, что она двоичная, это значит что она бинарная, а не унарная операция. Операция, у которой есть левая и правая части.
Что происходит во время выполнения?
Когда код доходит, это все подменяется на кусочки кода, которые уже у нас есть внутри компилятора, и компилятор знает, как хорошо работать с этим конкретным случаем, если мы имеем информацию о типе. То есть вот здесь подменяется на вызов кода, который знает, как получить primes из объекта:
Вот здесь подменяется на код, который знает, как получить элемент из массива SMI:
Здесь на код, который знает как посчитать остаток от деления двух SMI:
Он уже будет оптимизирован. И вот так примерно работал компилятор, насыщая такими кусочками.
Это, конечно, дает некоторый overhead, но и дает производительность.
У нас интернет развивался, увеличивалось количество JavaScript, требовалось большая производительность, и компания Google ответила созданием нового компилятора Crankshaft.
Старый компилятор стал называться FullCodegen, потому что он работает с полной кодовой базой, он знает весь JavaScript, как его компилировать. И он производит неоптимизированный код. Если он натыкается на какую-то функцию, которая вызывается несколько раз, он считает, что она стала горячей, и он знает, что компилятор Crankshaft может ее оптимизировать. И он отдает знания о типах и о том, что эту функцию можно оптимизировать в новый компилятор Crankshaft. Дальше новый компилятор заново получает абстрактное синтаксическое дерево. Это важно, что он получает не от старого компилятора AST, а снова идет и запрашивает AST. И зная о типах, делает оптимизацию, и на выходе мы получаем оптимизированный код.
Если он не может сделать оптимизацию, он сваливается в деоптимизацию. Когда это происходит? Вот как я сказал раньше, например, у нас в цикле Hidden Class крутится, потом неожиданное что-то и мы вывалились в деоптимизацию. Или, например, многие любят делать проверку, когда у нас есть что-то в левой части, и мы берем, например, длину, т.е. мы проверяем, есть ли у нас строка, и берем ее длину. Чем это плохо? Потому, что когда у нас строки нет, то в левой части у нас получается Boolean и на выходе получается Boolean, а до этого шел Number. И вот в этом случае мы сваливаемся в деоптимизацию. Или он встретил код, не может его оптимизировать.
На примере того же кода. Вот у нас был код насыщенный инлайн-кэшами, он весь инлайнится в новом компиляторе.
Он вставляет это все инлайново. Причем этот компилятор является спекулятивным оптимизирующим компилятором. На чем он спекулирует? Он спекулирует на знаниях о типах. Он предполагает, что если мы 10 раз вызвали с этим типом, то и дальше будет этот тип. Везде есть такие проверки на то, что пришел тот тип, который он ожидал, и когда приходит тип, которого он не ожидал, то он сваливается в деоптимизацию. Эти улучшения дали хороший прирост производительности, но постепенно команда, занимающаяся движком V8, поняла, что все надо начать с нуля. Почему? Вот есть такой способ разработки ПО, когда мы пишем первую версию, а вторую версию мы пишем с нуля, потому что мы поняли, как надо было писать. И создали новый компилятор – Turbofan в 2014 году.
У нас есть исходный код, который попадает в парсер, далее в компилятор FullCodegen. Так было до этого, никаких отличий. На выходе мы получаем неоптимизированный код. Если мы можем сделать какую-либо оптимизацию, то мы уходим в два компилятора, Crankshaft и Turbofan. FullCodegen сам решает, может ли оптимизировать конкретные вещи компилятор Turbofan, и если может, то отправляет в него, а если не может, то отправляет в старый компилятор. Туда постепенно стали добавлять новые конструкции из ES6. Начали с того, что заоптимизировали asm.js в него.
Зачем нужен новый компилятор?
Улучшить базовую производительность
Сделать производительность предсказуемой
Уменьшить сложность исходного кода
Что значит «улучшить базовую производительность»?
Старый компилятор был написан в те годы, когда у нас стояли мощные десктопы. И его тестировали на таких тестах, как octane, синтетических, которые проверяли пиковую производительность. Недавно была конференция Google I/O, и там менеджер, управляющий разработкой V8, завил, что они отказались в принципе от octane, потому что он не соответствует тому, с чем на самом деле работает компилятор. И это привело к тому, что у нас была очень хорошая пиковая производительность, но очень просела базовая, т.е. были не заоптимизированы вещи в коде, и когда код, хорошо работающий, натыкался на такие вещи, то шло значительное падение производительности. И таких операций скопилось много, вот несколько из них: forEach, map, reduce. Они написаны на обычном JS, нашпигованы проверками, сильно медленней, чем for. Часто советовали использовать for.
Медленная операция bind – она реализована внутри, оказывается, совершенно ужасно. Многие фреймворки писали свои реализации bind. Часто люди говорили, что я сел, написал на коленке bind и он работает быстрее, удивительно. Функции, содержащие try{}catch(e){}(и finally), – очень медленные.
Часто встречалась такая табличка, что лучше не использовать, чтобы не просела производительность. На самом деле код работает медленно, потому что компилятор работает неправильно. И с приходом Turbofan можно забыть об этом, потому что все уже заоптимизировано. Также очень важно: была улучшена производительность асинхронных функций.
Поэтому все ждут релиза новой node’ы, которая недавно вышла, там важна как раз производительность с async/await’ами. У нас язык асинхронный изначально, а пользоваться хорошо мы могли только callback’ами. И кто пишет с promise, знают, что их сторонние реализации работают быстрее, чем нативная реализация.
Следующей была задача сделать производительность предсказуемой. Была ситуация такая: код, который отлично показывал себя на jsPerf, при вставке в рабочий код показывал уже совсем другую производительность. Но и другие такие же случаи, когда мы не могли гарантировать, что наш код будет работать так же производительно, как мы изначально предполагали.
Например, у нас есть такой довольно простой код, который вызывает mymax, и если мы проверим его (при помощи ключей trace-opt и trace-deopt – показывают, какие функции были оптимизированы, а какие нет).
Мы можем запустить это с node, а можем и с D8 – специальной средой, где V8 работает отдельно от браузера. Она нам показывает, что оптимизации были отключены. Потому что слишком много раз запускался на проверку. В чем проблема? Оказывается, псевдомассив arguments — слишком большой, и внутри, оказывается, стояла проверка на размер этого массива. Причем эта проверка, как сказал Benedikt Meurer (ведущий разработчик Turbofan), не имела никакого смысла, она просто copypaste-ом с годами переходила.
И почему ограничена длина? Ведь не проверяется размер стека, ничего, вот просто так была ограничена. Это неожиданное поведение, от которого необходимо было избавляться.
Другой пример, вот у нас есть dispatcher, который вызывает два callback. Так же, если мы его вызовем, то увидим, что он был деоптимизирован. В чем здесь проблема? В том, что одна функция является strict, а вторая не strict. И у них в старом компиляторе получаются разные hidden classes. Т.е. он считает их разными. И в этом случае он так же уходит на деоптимизацию. И этот, и предыдущий код, он написан в принципе правильно, но он деоптимизируется. Это неожиданно.
Еще был вот такой пример в твиттере, когда оказалось, что в некоторых случаях цикл for в chrome работал даже медленнее, чем reduce. Хотя мы знаем, что reduce медленнее. Оказалось, проблема в том, что внутри for использовался let – неожиданно. Я поставил даже последнюю версию на тот момент и результат уже хороший – исправили.
Следующий пункт был — уменьшить сложность. Вот у нас была версия V8 3.24.9 и она поддерживала четыре архитектуры.
Сейчас же V8 поддерживает девять архитектур!
И код копился годами. Он был написан частично на C, Assembler, JS, и вот так примерно ощущал себя разработчик, который приходил в команду.
Код должен легко изменяться, чтобы можно было реагировать на изменения в мире. И с введением Turbofan количество архитектурно-специфичного кода уменьшилось.
С 2013 по 2017 года стало на 29% меньше архитектурно-специфичного кода. Это произошло за счет появления новой архитектуры генерации кода в Turbofan.
Они ее сделали управляемой данными, т.е. у нас есть граф управляющего потока, который содержит данные и знание о том, что с ними должно произойти. И он попадает в общий селектор команд, потом происходит резервирование регистров и далее генерация кода под разные архитектуры. Т.е. разработчику уже не надо знать, как написано все под конкретные архитектуры, а можно сделать более общий код. Вот так все происходило, хорошо улучшали код, но постепенно через несколько лет, после того как написали компилятор для интерпретируемого языка, оказалось, что все же нужен интерпретатор.
А в чем причина? Причину держит в руках Стив Джобс.
Это, конечно, не сам iPhone, а те смартфоны, которые породил iPhone, которые дали удобный доступ в интернет. И это привело к тому, что количество пользователей на мобильных устройствах превысило количество на десктопах.
А первоначально компиляторы разрабатывались для мощных десктопов, а не для мобильных устройств.
Вот схема времени первичного анализа 1МБ JavaScript. И недавно был вопрос, почему ВКонтакте делает серверный рендеринг, а не клиентский. Потому что время, потраченное на анализ JS, может быть в 2-5 раз больше на мобильных устройствах. И это мы говорим о топовых устройствах, а люди зачастую ходят с совсем другими.
И еще одна проблема: у многих китайских устройствах памяти 512 МБ, а если посмотреть, как происходит распределение памяти V8, то появляется еще одна проблема.
Память делится на объекты (то что использует наш код) и кодовые объекты (это то, что использует сам компилятор — например, хранит там inline caches). Получается, что 30% памяти занято виртуальной машиной для поддержки внутреннего использования. Мы не можем этой памятью управлять, ее сам компилятор потребляет.
С этим необходимо было что-то делать, и в 2016 году команда разработчиков Android из Лондона ответила созданием нового интерпретатора Ignition.
Вы могли заметить, что код, оптимизированный компилятором Turbofan, не обращается за синтаксическим деревом в парсер, а получает что-то из интерпретатора. Он получает байт-код.
Теперь абстрактное синтаксическое дерево парсится в байт-код, и этот парсинг JavaScript происходит один раз, дальше используется байткод.
Если кто-то не знает, байткод – это компактное платформонезависимое представление программы. Это чем-то похоже на assembler, только платформонезависимый. Так он называется, потому что все инструкции занимают один байт.
Посмотрим, как для такого кусочка программы генерируется байткод.
У нас есть программа, у нас есть сгенерированный байткод и у нас есть набор регистров.
Так мы устанавливаем значения для этих регистров входные, которые попадут в нашу программу. На первом этапе мы загружаем в специальный регистр accumulator (он нужен для того, чтобы не расходовать лишний раз регистры, а участвует только в вычислениях) smi integer равный 100.
Следующая команда нам говорит, что нужно вычесть из регистра a2 (в табличке видим там 150) предыдущее значение аккумулятора (100). В accumulator мы получили 50.
Дальше нам команда говорит, что нужно сохранить в r0. Он связан с переменной d.
Дальше становится более понятно. Снова загружаем значение из b, умножаем на значение accumulator, добавляем a0 и получаем на выходе, соответственно, 105.
И вся наша программа превращается в длинную цепочку из байткода. Таким образом уменьшилось потребление памяти, которое уходило на хранение нашего кода.
Была вторая проблема, это память, которую потребляли inline caches. Для этого перешли на новые кеши – Data-driven IC, которые уменьшают стоимость медленного пути. Медленный путь – это как работает не оптимизированный код, быстрый код – когда он оптимизирован.
Слева мы видим старую схему. Когда нам нужно найти какое-либо поле в объекте, мы храним знание о том, где оно лежит в объекте, где-то храним этот объект и умеем с ним обращаться. В новой схеме существует управляющий вектор, в котором есть данные и команды, и знание о том, что с этими командами делать. И он проходит по загрузке inline caches, на быстрый путь, если деоптимизация, то на медленный путь. И, соответственно, эта схема уже не требует хранения обращений к объектам, и она получается компактней. В итоге после внедрения схемы потребление памяти на одинаковом коде уменьшилось.
И наконец в этом году схема сильно упростилась.
Здесь мы всегда работаем в компиляторе Turbofan. Можно заметить, что раньше компилятор FullCodegen знал весь JS, а компилятор Crankshaft — только часть JS, а теперь компилятор Turbofan знает весь JS и работает со всем JS. Если он не может оптимизировать, он выдает неоптимизированный код, если может, то, соответственно, оптимизированный. И за кодом он обращается в интерпретатор.
У нас есть классы, которые не видны (многие знают, что в ES6 есть новые классы, но это просто сахар). За ними необходимо следить, ибо код для хорошей производительности должен быть мономорфным, а не полиморфным. Т.е. если у нас изменяются входящие в функцию классы, у них меняется hidden class – у объектов, которые попадают в нашу функцию, то код становится полиморфным и он плохо оптимизируется. Если у нас объекты приходят одного типа hidden class, то, соответственно, код мономорфный.
В V8 код проходит через интерпретатор и JIT-компилятор. Задача JIT-компилятора — сделать код быстрее. JIT-компилятор прогоняет наш код в цикле и каждый раз, основываясь на данных, которые получил в прошлый раз, пытается сделать код лучше. Он наполняет его знанием о типах, которые получает при первом прогоне, и за счет этого делает какие-то оптимизации. В нем внутри лежат кусочки, которые ориентированы для работы с максимальной производительностью. Если у нас в коде есть a+b – это медленно. Мы знаем, что это number+number или string+string, мы можем сделать это быстро. Вот этим занимается JIT-компилятор.
Чем лучше оптимизация, тем выше накладные расходы (время, память). Задача же интерпретатора – уменьшить накладные расходы на память. Даже с приходом Turbofan отказались от некоторых оптимизаций, которые были ранее, потому что решили повышать базовую производительность и немного снижать пиковую.
В компиляторе есть два режима работы – холодный и горячий. Холодный, это когда наша функция запущена в первый раз. Если функция запускалась несколько раз, то компилятор понимает, что она уже горяча, и пытается ее оптимизировать. Здесь есть засада с тестами. Когда разработчики гоняют тесты по много раз и получают какие-либо данные, то это уже оптимизированный горячий код. А в реальности этот код может быть вызван один-два раза и показывать совсем другую производительность. Это необходимо учитывать.
С мономорфным кодом такой же пример. То есть когда мы пишем код, мы можем помочь нашему компилятору. Мы можем писать код так, словно у нас типизированный язык (не переопределять переменные, не записывать в них разные типы, возвращать всегда один и тот же тип).
Вот и все основные секреты.
Ссылки для чтения github.com/v8/v8/wiki/TurboFan http://benediktmeurer.de/ http://mrale.ph/ http://darksi.de/ https://medium.com/@amel_true
Если вы любите JS так же, как мы, и с удовольствием копаетесь во всей его нутрянке, вам могут быть интересные вот эти доклады на грядущей московской конференции
HolyJS
:
Движок JavaScript: что внутри. Разберёмся, как работает движок V8 и… | by Андрей Шагин | NOP::Nuances of Programming
Чтобы разобраться в том, как работает механизм обработки кода (иначе говоря, движок JavaScript), надо понять, что происходит при выполнении кода. Такие знания помогают программистам писать лучший, более быстрый и умный код.
Движки JavaScript — это не что иное, как программы, преобразующие код на JavaScript в код более низкого уровня, который компьютер сможет понять. Эти движки встроены в браузеры и веб-серверы (Node.js), что даёт возможность выполнять код и осуществлять компиляцию во время выполнения.
Разве JavaScript — это не интерпретируемый язык?
Краткий ответ: это зависит от реализации. Обычно JavaScript относят к интерпретируемым языкам, хотя вообще-то он компилируется. Современные компиляторы JavaScript фактически выполняют JIT-компиляцию, т.е. компиляцию «на лету», которая осуществляется во время работы программы.
Существует множество разных движков. У каждого из них внутри есть что-то вроде конвейера с интерпретатором и конвейера с оптимизатором и компилятором. Интерпретатор генерирует байт-код, а оптимизатор выдаёт оптимизированный машинный код. Далее в статье в качестве примера будет использоваться движок V8.
V8 — это высокопроизводительный движок от Google с открытым исходным кодом JavaScript и WebAssembly, написанный на языке C++.Он используется в Chrome, Node.js и других платформах и реализует ECMAScript и WebAssembly (см.v8.dev).
Всякий раз, когда JavaScript-код отправляется в движок V8, он проходит ряд этапов для отображения console.log:
Парсер
Движок выполняет то, что мы называем лексическим анализом. Это первое, что происходит с файлом JavaScript при попадании в движок. Код разбивается на части, называемые токенами, для выявления их назначения, после чего мы узнаём, что код пытается сделать.
Абстрактное синтаксическое дерево (AST)
На основе этих токенов создаётся то, что мы называем AST. Синтаксическое дерево — это древовидное представление синтаксической структуры кода JavaScript, и мы можем использовать этот инструмент для анализа преобразования кода AST.
Интерпретатор
Интерпретатор читает файлы JavaScript построчно и преобразовывает их на ходу (во время работы программы, не прерывая её выполнение). На основе сгенерированного кода AST интерпретатор начинает быстро создавать байт-код. Никаких оптимизаций здесь не выполняется, так что байт-код этот неоптимизированный.
Байт-код не является таким низкоуровневым, как машинный код, но всё же может быть интерпретирован движком JavaScript для выполнения кода.
Профайлер
Профайлер отвечает за проверку кода. Он вызывает специальное средство контроля, которое отслеживает код и наблюдает за ходом его выполнения, обращая наше внимание на то, как можно оптимизировать код. Выдаёт, например, информацию о том, сколько раз код запускался, какие типы используются и как мы можем его оптимизировать?
Так что, если профайлер находит часть кода, которую можно оптимизировать, он передаёт этот код JIT-компилятору, чтобы он мог быть скомпилирован и выполнялся быстрее. Рассмотрим эти конвейеры с интерпретатором и компилятором более подробно.
Оптимизирующий компилятор
Задача оптимизирующего компилятора — определить, что делает программа, подлежащая оптимизации, и создать из неё оптимизированную программу, выполняющую всё то же самое, только быстрее.
Он не преобразует файлы на лету. Он делает свою работу заранее, создавая преобразование только что написанного кода и компилируя его в язык, понятный для компьютера.
Деоптимизация
Оптимизирующий компилятор на основе имеющихся у него данных профилирования делает определённые предположения, а затем выдаёт высокооптимизированный машинный код. Но вполне возможно, что в какой-то момент одно из предположений окажется неверным. И тогда оптимизирующий компилятор может деоптимизировать код.
JavaScript — это динамический язык программирования, а это подразумевает, что свойства могут легко добавляться или удаляться из объекта после его создания. Для написания более лучшего кода надо понимать, как JavaScript определяет объекты и как движок работает с объектами и свойствами.
Enumerable → определяет, перечисляется ли свойство в циклах for—in.
Value → само значение.
Writable → определяет, можно ли свойство изменить.
Configurable → определяет, можно ли удалить свойство.
Оптимизация доступа к свойству
В динамических языках, таких как JavaScript, для доступа к свойствам требуется ряд инструкций. Поэтому почти в каждом движке имеется оптимизация для ускорения этого доступа. Такая оптимизация в V8 реализуется скрытыми классами: V8 присоединяет скрытый класс к каждому отдельному объекту. Целью скрытых классов является оптимизация времени доступа к свойствам.
Скрытые классы работают аналогично фиксированным макетам (классам) объектов, используемым в таких языках, как Java (за исключением того, что они создаются во время выполнения). Магия здесь в том, что несколько объектов могут иметь один и тот же скрытый класс, поэтому необходимы только минимальные ресурсы, а код становится быстрее:
Выгода становится очевидной, когда объектов много. Ведь пока у них один и тот же скрытый класс, информацию приходится сохранять только один раз независимо от того, сколько всего имеется объектов!
Тема оптимизации очень обширна и может стать предметом обсуждения отдельной статьи.
Цель встроенного кэширования в том, чтобы ускорить привязку метода времени выполнения. Происходит это за счёт запоминания результатов поиска предыдущего метода непосредственно в месте вызова.Встроенное кэширование особенно полезно для динамически типизированных языков, где большинство, если не все привязки методов происходят во время выполнения и где виртуальные таблицы методов часто не могут быть использованы.
Основная причина существования скрытых классов — концепция встроенных кэшей. Движки JavaScript используют встроенные кэши для запоминания информации о том, где найти свойства в объектах. Поэтому нет необходимости повторять дорогостоящий поиск свойств при каждом доступе к ним. Зачем каждый раз искать свойства, когда со встроенными кэшами это значительно быстрее?
инициализировать объекты лучше всегда одним и тем же способом, чтобы скрытые классы у них не были разными;
с атрибутами свойств элементов массива надо быть осторожным, чтобы они могли аккуратно сохраняться, а работа с ними была эффективной.
Читайте также:
Читайте нас в телеграмме, vk и Яндекс.Дзен
Двигатель JavaScript V8
V8 — это название движка JavaScript, на котором работает Google Chrome. Это то, что берет наш JavaScript и выполняет его при просмотре в Chrome.
V8 предоставляет среду выполнения, в которой выполняется JavaScript. ВДОМ, и другиеAPI веб-платформыпредоставляются браузером.
Замечательно то, что движок JavaScript не зависит от браузера, в котором он размещен. Эта ключевая особенность способствовала ростуNode.js. V8 был выбран в качестве движка, на котором работал Node.js еще в 2009 году, и когда популярность Node.js резко возросла, V8 стал движком, который теперь поддерживает невероятное количество серверного кода, написанного на JavaScript.
Экосистема Node.js огромна, и благодаря ей V8 также поддерживает настольные приложения с такими проектами, как Electron.
Другие движки JS
В других браузерах есть собственный движок JavaScript:
Firefox имеетОбезьяна паук
Safari имеетJavaScriptCore(также называется нитро)
Edge имеетЧакра
и многие другие тоже существуют.
Все эти двигатели реализуют стандарт ECMA ES-262, также называемыйECMAScript, стандарт, используемый JavaScript.
В поисках производительности
V8 написан на C ++ и постоянно совершенствуется. Он портативен и работает на Mac, Windows, Linux и некоторых других системах.
В этом введении V8 я проигнорирую детали реализации V8: их можно найти на более авторитетных сайтах (например, на официальном сайте V8), и они со временем меняются, часто радикально.
V8 постоянно развивается, как и другие движки JavaScript, чтобы ускорить Интернет и экосистему Node.js.
В Интернете идет гонка за производительностью, которая продолжается годами, и мы (как пользователи и разработчики) извлекаем большую выгоду из этой конкуренции, потому что год за годом мы получаем более быстрые и оптимизированные машины.
Компиляция
JavaScript обычно считается интерпретируемым языком, но современные движки JavaScript больше не просто интерпретируют JavaScript, они его компилируют.
Это происходит с 2009 года, когда в Firefox 3.5 был добавлен компилятор JavaScript SpiderMonkey, и все последовали этой идее.
Это может показаться нелогичным, но с момента появления Google Maps в 2004 году JavaScript превратился из языка, который обычно выполнял несколько десятков строк кода для завершения приложений с тысячами и сотнями тысяч строк, выполняемых в браузере.
Наши приложения теперь могут работать в течение нескольких часов в браузере, а не представляют собой всего лишь несколько правил проверки форм или простых скриптов.
В этомНовый мир, компиляция JavaScript имеет смысл, потому что, хотя для JavaScript может потребоваться немного больше времени,готовы, после завершения он будет намного более производительным, чем чисто интерпретируемый код.
Больше руководств по инструментам разработчика:
Эксперты взялись за главную дыру Google Chrome — JavaScript-движок V8
Ввиду роста количества атак в рунете Национальный координационный центр по компьютерным инцидентам (НКЦКИ) опубликовал рекомендации по повышению уровня защищенности веб-приложений и минимизации рисков для пользователей.
При проведении атак на пользователей веб-приложений злоумышленники зачастую используют зависимости в коде, обеспечивающие взаимодействие со страницами сайта на стороне клиента, — подключаемые JavaScript-библиотеки, фреймворки CSS, информационные, аналитические и защитные плагины, а также шрифты, подгружаемые со сторонних серверов. Подмена таких модулей позволяет авторам атак размещать на сайтах вредоносные скрипты, собирать конфиденциальные данные посредством фишинга, перенаправлять пользователей на потенциально опасные ресурсы.
Для повышения безопасности веб-приложений и предотвращения злонамеренного вмешательства в их работу НКЦКИ рекомендует (PDF) принять следующие меры:
Предоставить законным пользователям возможность самостоятельно завершать сеансы работы в веб-приложении.
Обеспечить гарантированное удаление идентификатора сессии по ее завершении на стороне клиента.
Ввести обязательную аутентификацию для доступа к защищенным ресурсам веб-приложений и внесения изменений в профили пользователя.
Обеспечить криптозащиту аутентификационных данных пользователей при хранении.
Исключить хранение аутентификационных данных и информации о структуре каталогов на сервере в файлах, доступных по URL.
Запретить использование заголовка REFERER в качестве основного механизма авторизации.
Использовать параметризованные запросы к базам данных SQL, либо применить санацию пользовательского ввода.
Использовать фильтрацию входного потока данных.
Запретить использование HTML-тегов <img> и <table> в пользовательском вводе.
Очистить сообщения об ошибках от такой информации, как данные о структуре файловой системы, фрагменты кода, подробности обращения к СУБД; ввести в обиход страницу-заглушку с кодом ошибки 200.
Использовать надежные протоколы для аутентификации пользователей (Kerberos, TLS 1.2 и выше).
Использовать POST-запросы для передачи аутентификационных данных на сервер.
Исключить использование внешних сущностей, внешних параметров сущностей и внешних описаний типа документа при обработке данных в формате XML.
Запретить кеширование конфиденциальных данных, вводимых в веб-формы, ограничить доступ к содержимому куки-файлов с помощью атрибутов HTTPOnly и secure.
Обеспечить проверку корректности вводимых пользователем данных и на стороне клиента, и на стороне сервера.
Включить в HTTP-заголовки сведения об используемой кодировке и запретить ее замену для всех источников входных данных.
Ввести проверку благонадежности стороннего JavaScript-кода перед его использованием.
Регулярно проверять хеш-суммы файлов JavaScript; при обнаружении изменений следует немедленно прекратить использование кода и повторно проверить функциональность.
Отказаться от использования динамически формируемых JavaScript.
Отдавать предпочтение загрузке внешних зависимостей из контролируемых источников.
Разработать и поддерживать в актуальном состоянии планы реагирования на DDoS-атаки, утечки, дефейс сайта, несанкционированный доступ к интерфейсу управления.
Почти всё там изложенное я уже успел узнать сам из разных источников.
Рассказано, что движок JavaScript «V8» встроен в браузер «Google Chrome» и отвечает там за выполнение скриптов на языке JavaScript. Движок JavaScript «V8» не зависит от программы (изначально браузера), в которую он встроен. Поэтому стало возможным появление среды выполнения «Node.js», которая тоже использует этот движок.
Область применения среды выполнения «Node.js» огромна. В частности, благодаря движку JavaScript «V8», среду выполнения «Node.js» можно использовать для написания обычных приложений для настольного компьютера (по-английски «desktop applications» или «desktop apps») с помощью таких проектов, как фреймворк «Electron».
С помощью фреймворка «Electron» создан, к примеру, очень популярный сейчас редактор кода «Visual Studio Code».
Другие браузеры используют их собственные движки JavaScript. Например:
– браузер «Mozilla Firefox» использует движок JavaScript «SpiderMonkey»: https://spidermonkey.dev
– браузер «Safari» (разработанный компанией «Apple») использует движок JavaScript «JavaScriptCore» (также его называют «Nitro»): https://developer.apple.com/documentation/javascriptcore
– браузер «Microsoft Edge» первоначально использовал движок JavaScript «Chakra»: https://github.com/chakra-core/ChakraCore
Но сейчас браузер «Microsoft Edge» перестроен для работы на базе браузера «Chromium» и поэтому стал использовать движок JavaScript «V8».
Кроме перечисленных существуют и другие движки JavaScript.
Движок JavaScript «V8» написан на языке программирования «C++» и постоянно улучшается. Он портирован для работы в операционных системах «macOS», «Windows», «Linux» и некоторых других. Детали реализации этого движка можно найти на его официальном сайте:
https://v8.dev
Все движки JavaScript участвуют в гонке за производительностью (скоростью работы). Изначально язык программирования JavaScript считался интерпретируемым языком программирования, но сейчас это не так: современные движки компилируют код.
Ускорение работы движка дало программистам возможность увеличить размер программ, написанных на языке JavaScript, с нескольких десятков строк кода до полноценных приложений, включающих от тысяч до сотен тысяч строк кода.
Движок JavaScript: что внутри
Введение
Чтобы разобраться в том, как работает механизм обработки кода (иначе говоря, движок JavaScript), надо понять, что происходит при выполнении кода. Такие знания помогают программистам писать лучший, более быстрый и умный код.
Движки JavaScript — это не что иное, как программы, преобразующие код на JavaScript в код более низкого уровня, который компьютер сможет понять. Эти движки встроены в браузеры и веб-серверы (Node.js), что даёт возможность выполнять код и осуществлять компиляцию во время выполнения.
Разве JavaScript — это не интерпретируемый язык?
Краткий ответ: это зависит от реализации. Обычно JavaScript относят к интерпретируемым языкам, хотя вообще-то он компилируется. Современные компиляторы JavaScript фактически выполняют JIT-компиляцию, т.е. компиляцию «на лету», которая осуществляется во время работы программы.
Движок
Существует множество разных движков. У каждого из них внутри есть что-то вроде конвейера с интерпретатором и конвейера с оптимизатором и компилятором. Интерпретатор генерирует байт-код, а оптимизатор выдаёт оптимизированный машинный код. Далее в статье в качестве примера будет использоваться движок V8.
V8 — это высокопроизводительный движок от Google с открытым исходным кодом JavaScript и WebAssembly, написанный на языке C++.Он используется в Chrome, Node.js и других платформах и реализует ECMAScript и WebAssembly (см.v8.dev).
Что внутри движка
Всякий раз, когда JavaScript-код отправляется в движок V8, он проходит ряд этапов для отображения console.log:
Парсер
Движок выполняет то, что мы называем лексическим анализом. Это первое, что происходит с файлом JavaScript при попадании в движок. Код разбивается на части, называемые токенами, для выявления их назначения, после чего мы узнаём, что код пытается сделать.
Абстрактное синтаксическое дерево (AST)
На основе этих токенов создаётся то, что мы называем AST. Синтаксическое дерево — это древовидное представление синтаксической структуры кода JavaScript, и мы можем использовать этот инструмент для анализа преобразования кода AST.
Интерпретатор
Интерпретатор читает файлы JavaScript построчно и преобразовывает их на ходу (во время работы программы, не прерывая её выполнение). На основе сгенерированного кода AST интерпретатор начинает быстро создавать байт-код. Никаких оптимизаций здесь не выполняется, так что байт-код этот неоптимизированный.
Байт-код не является таким низкоуровневым, как машинный код, но всё же может быть интерпретирован движком JavaScript для выполнения кода.
Профайлер
Профайлер отвечает за проверку кода. Он вызывает специальное средство контроля, которое отслеживает код и наблюдает за ходом его выполнения, обращая наше внимание на то, как можно оптимизировать код. Выдаёт, например, информацию о том, сколько раз код запускался, какие типы используются и как мы можем его оптимизировать?
Так что, если профайлер находит часть кода, которую можно оптимизировать, он передаёт этот код JIT-компилятору, чтобы он мог быть скомпилирован и выполнялся быстрее. Рассмотрим эти конвейеры с интерпретатором и компилятором более подробно.
Оптимизирующий компилятор
Задача оптимизирующего компилятора — определить, что делает программа, подлежащая оптимизации, и создать из неё оптимизированную программу, выполняющую всё то же самое, только быстрее.
Он не преобразует файлы на лету. Он делает свою работу заранее, создавая преобразование только что написанного кода и компилируя его в язык, понятный для компьютера.
Деоптимизация
Оптимизирующий компилятор на основе имеющихся у него данных профилирования делает определённые предположения, а затем выдаёт высокооптимизированный машинный код. Но вполне возможно, что в какой-то момент одно из предположений окажется неверным. И тогда оптимизирующий компилятор может деоптимизировать код.
Объектная модель JavaScript
JavaScript — это динамический язык программирования, а это подразумевает, что свойства могут легко добавляться или удаляться из объекта после его создания. Для написания более лучшего кода надо понимать, как JavaScript определяет объекты и как движок работает с объектами и свойствами.
Enumerable → определяет, перечисляется ли свойство в циклах for—in.
Value → само значение.
Writable → определяет, можно ли свойство изменить.
Configurable → определяет, можно ли удалить свойство.
Оптимизация доступа к свойству
В динамических языках, таких как JavaScript, для доступа к свойствам требуется ряд инструкций. Поэтому почти в каждом движке имеется оптимизация для ускорения этого доступа. Такая оптимизация в V8 реализуется скрытыми классами: V8 присоединяет скрытый класс к каждому отдельному объекту. Целью скрытых классов является оптимизация времени доступа к свойствам.
Скрытые классы работают аналогично фиксированным макетам (классам) объектов, используемым в таких языках, как Java (за исключением того, что они создаются во время выполнения). Магия здесь в том, что несколько объектов могут иметь один и тот же скрытый класс, поэтому необходимы только минимальные ресурсы, а код становится быстрее:
Выгода становится очевидной, когда объектов много. Ведь пока у них один и тот же скрытый класс, информацию приходится сохранять только один раз независимо от того, сколько всего имеется объектов!
Тема оптимизации очень обширна и может стать предметом обсуждения отдельной статьи.
Встроенные кэши
Цель встроенного кэширования в том, чтобы ускорить привязку метода времени выполнения. Происходит это за счёт запоминания результатов поиска предыдущего метода непосредственно в месте вызова.Встроенное кэширование особенно полезно для динамически типизированных языков, где большинство, если не все привязки методов происходят во время выполнения и где виртуальные таблицы методов часто не могут быть использованы.
Основная причина существования скрытых классов — концепция встроенных кэшей. Движки JavaScript используют встроенные кэши для запоминания информации о том, где найти свойства в объектах. Поэтому нет необходимости повторять дорогостоящий поиск свойств при каждом доступе к ним. Зачем каждый раз искать свойства, когда со встроенными кэшами это значительно быстрее?
Выводы
инициализировать объекты лучше всегда одним и тем же способом, чтобы скрытые классы у них не были разными;
с атрибутами свойств элементов массива надо быть осторожным, чтобы они могли аккуратно сохраняться, а работа с ними была эффективной.
Читайте также:
Перевод статьи Leonardo Freitas: Inside the JavaScript Engine
Как работает JavaScript: под капотом движка V8
Сегодня мы заглянем под капот движка JavaScript V8 и выясним, как именно выполняется JavaScript.
Задний план
Веб-стандарты — это набор правил, которые реализует браузер. Они определяют и описывают аспекты Всемирной паутины.
W3C — это международное сообщество, которое разрабатывает открытые стандарты для Интернета. Они следят за тем, чтобы все следовали одним и тем же принципам и не поддерживали десятки совершенно разных сред.
Современный браузер — это довольно сложная программа с кодовой базой, состоящей из десятков миллионов строк кода. Таким образом, он разделен на множество модулей, отвечающих за разную логику.
Двумя наиболее важными частями браузера являются движок JavaScript и движок рендеринга.
Blink — это движок рендеринга, который отвечает за весь конвейер рендеринга, включая деревья DOM, стили, события и интеграцию V8. Он анализирует дерево DOM, определяет стили и определяет визуальную геометрию всех элементов.
Непрерывно отслеживая динамические изменения с помощью кадров анимации, Blink раскрашивает контент на вашем экране. Движок JS — большая часть браузера, но мы еще не вдавались в подробности.
Движок JavaScript 101
Механизм JavaScript выполняет и компилирует JavaScript в собственный машинный код. Каждый крупный браузер разработал свой собственный JS-движок: Google Chrome использует V8, Safari использует JavaScriptCore, а Firefox использует SpiderMonkey.
В частности, мы будем работать с V8, поскольку он используется в Node.js и Electron, но другие движки построены таким же образом.
Каждый шаг будет содержать ссылку на код, отвечающий за него, чтобы вы могли ознакомиться с кодовой базой и продолжить исследование после этой статьи.
Мы будем работать с зеркалом V8 на GitHub, поскольку оно предоставляет удобный и хорошо известный пользовательский интерфейс для навигации по кодовой базе.
Подготовка исходного кода
Первое, что нужно сделать V8, — это загрузить исходный код. Это можно сделать через сеть, кэш или сервис-воркеры.
Как только код получен, нам нужно изменить его так, чтобы компилятор мог его понять. Этот процесс называется парсингом и состоит из двух частей: сканера и самого парсера.
Сканер берет файл JS и преобразует его в список известных токенов. Список всех токенов JS находится в файле keywords.txt.
Анализатор поднимает его вверх и создает абстрактное синтаксическое дерево (AST): древовидное представление исходного кода. Каждый узел дерева обозначает конструкцию, встречающуюся в коде.
Давайте посмотрим на простой пример:
function foo() {
let bar = 1;
return bar;
}
Этот код создаст следующую древовидную структуру:
Пример дерева AST
Вы можете выполнить этот код, выполнив обход предварительного заказа (корень, влево, вправо):
Определите функцию foo.
Объявите переменную bar.
Назначьте 1 в bar.
Верните bar из функции.
Вы также увидите VariableProxy — элемент, который связывает абстрактную переменную с местом в памяти. Процесс разрешения VariableProxy называется анализом объема.
В нашем примере результат процесса VariableProxy будет указывать на одну и ту же переменную bar.
Парадигма Just-in-Time (JIT)
Обычно для выполнения кода язык программирования необходимо преобразовать в машинный код. Есть несколько подходов к тому, как и когда может произойти это преобразование.
Наиболее распространенный способ преобразования кода — выполнение предварительной компиляции. Это работает именно так, как звучит: код преобразуется в машинный код перед выполнением вашей программы на этапе компиляции.
Этот подход используется многими языками программирования, такими как C ++, Java и другими.
С другой стороны таблицы у нас есть интерпретация: каждая строка кода будет выполняться во время выполнения. Этот подход обычно используется в языках с динамической типизацией, таких как JavaScript и Python, поскольку невозможно узнать точный тип до выполнения.
Поскольку предварительная компиляция позволяет оценить весь код вместе, она может обеспечить лучшую оптимизацию и в конечном итоге произвести более производительный код. С другой стороны, интерпретацию проще реализовать, но обычно она медленнее, чем скомпилированный вариант.
Чтобы преобразовать код для динамических языков быстрее и эффективнее, был создан новый подход, названный компиляцией Just-in-Time (JIT). Он сочетает в себе лучшее из интерпретации и компиляции.
Используя интерпретацию как базовый метод, V8 может обнаруживать функции, которые используются чаще, чем другие, и компилировать их, используя информацию о типе из предыдущих выполнений.
Однако есть вероятность, что тип может измениться. Вместо этого нам нужно деоптимизировать скомпилированный код и вернуться к интерпретации (после этого мы можем перекомпилировать функцию после получения обратной связи нового типа).
Давайте рассмотрим каждую часть JIT-компиляции более подробно.
Переводчик
V8 использует интерпретатор под названием Ignition. Первоначально он берет абстрактное синтаксическое дерево и генерирует байтовый код.
Инструкции байтового кода также имеют метаданные, такие как позиции исходной строки для будущей отладки. Как правило, инструкции байтового кода соответствуют абстракциям JS.
Теперь возьмем наш пример и сгенерируем для него байт-код вручную:
LdaSmi #1 // write 1 to accumulator
Star r0 // read to r0 (bar) from accumulator
Ldar r0 // write from r0 (bar) to accumulator
Return // returns accumulator
В Ignition есть так называемый аккумулятор — место, где вы можете хранить / читать значения.
Аккумулятор избавляет от необходимости толкать и выдвигать верхнюю часть стопки. Это также неявный аргумент для многих байт-кодов и обычно содержит результат операции. Return неявно возвращает аккумулятор.
Вы можете проверить весь доступный байтовый код в соответствующем исходном коде. Если вам интересно, как другие концепции JS (например, циклы async / await) представлены в байтовом коде, я считаю полезным прочитать эти ожидания тестирования.
Исполнение
После генерации Ignition интерпретирует инструкции, используя таблицу обработчиков, привязанных к байтовому коду. Для каждого байтового кода Ignition может искать соответствующие функции-обработчики и выполнять их с предоставленными аргументами.
Как мы упоминали ранее, этап выполнения также обеспечивает обратную связь типа о коде. Разберемся, как его собирают и управляют.
Во-первых, мы должны обсудить, как объекты JavaScript могут быть представлены в памяти. При наивном подходе мы можем создать словарь для каждого объекта и связать его с памятью.
Первый подход к хранению объекта
Однако обычно у нас много объектов с одинаковой структурой, поэтому было бы неэффективно хранить много дублированных словарей.
Чтобы решить эту проблему, V8 отделяет структуру объекта от самих значений с помощью форм объекта (или внутренних карт) и вектора значений в памяти.
Например, мы создаем литерал объекта:
let c = { x: 3 }
let d = { x: 5 }
c.y = 4
В первой строке он создаст фигуру Map[c], имеющую свойство x со смещением 0.
Во второй строке V8 повторно использует ту же форму для новой переменной.
После третьей строки он создаст новую форму Map[c1] для свойства y со смещением 1 и создаст ссылку на предыдущую форму Map[c].
Пример формы объекта
В приведенном выше примере вы можете видеть, что каждый объект может иметь ссылку на форму объекта, где для каждого имени свойства V8 может найти смещение для значения в памяти.
Формы объектов — это, по сути, связанные списки. Итак, если вы напишете c.x, V8 перейдет к началу списка, найдет там y, перейдет к связанной фигуре и, наконец, получит x и прочитает смещение от нее. Затем он перейдет к вектору памяти и вернет из него первый элемент.
Как вы понимаете, в большом веб-приложении вы увидите огромное количество связанных фигур. В то же время для поиска в связанном списке требуется линейное время, что делает поиск свойств действительно дорогостоящей операцией.
Чтобы решить эту проблему в V8, вы можете использовать встроенный кэш (IC). Он запоминает информацию о том, где найти свойства объектов, чтобы сократить количество поисков.
Вы можете думать об этом как о сайте для прослушивания в вашем коде: он отслеживает все события CALL, STORE и LOAD в функции и записывает все проходящие фигуры.
Структура данных для хранения IC называется вектором обратной связи. Это просто массив для хранения всех микросхем для функции.
function load(a) {
return a.key;
}
Для приведенной выше функции вектор обратной связи будет выглядеть следующим образом:
[{ slot: 0, icType: LOAD, value: UNINIT }]
Это простая функция только с одной ИС, которая имеет тип НАГРУЗКИ и значение UNINIT. Это означает, что он не инициализирован, и мы не знаем, что будет дальше.
Давайте вызовем эту функцию с разными аргументами и посмотрим, как изменится встроенный кэш.
let first = { key: 'first' } // shape A
let fast = { key: 'fast' } // the same shape A
let slow = { foo: 'slow' } // new shape B
load(first)
load(fast)
load(slow)
После первого вызова функции load наш встроенный кеш получит обновленное значение:
[{ slot: 0, icType: LOAD, value: MONO(A) }]
Это значение теперь становится мономорфным, что означает, что этот кеш может разрешиться только для формы A.
После второго вызова V8 проверит значение IC и увидит, что оно мономорфно и имеет ту же форму, что и переменнаяfast. Таким образом, он быстро вернет смещение и разрешит его.
В третий раз форма отличается от сохраненной. Таким образом, V8 вручную разрешит это и обновит значение до полиморфного состояния с помощью массива из двух возможных форм.
[{ slot: 0, icType: LOAD, value: POLY[A,B] }]
Теперь каждый раз, когда мы вызываем эту функцию, V8 необходимо проверять не только одну форму, но и перебирать несколько вариантов.
Для более быстрого кода вы можете инициализировать объекты одним и тем же типом и не слишком сильно менять их структуру.
Примечание. Вы можете помнить об этом, но не делайте этого, если это приведет к дублированию кода или к менее выразительному коду.
Встроенные кеши также отслеживают, как часто они вызываются, чтобы решить, подходит ли он для оптимизации компилятора — Turbofan.
Компилятор
Зажигание только доходит до нас. Если функция становится достаточно горячей, она будет оптимизирована в компиляторе Turbofan, чтобы сделать ее быстрее.
Турбовентилятор берет байтовый код из Ignition и набирает обратную связь (вектор обратной связи) для функции, применяет набор сокращений на его основе и создает машинный код.
Как мы видели ранее, обратная связь типа не гарантирует, что она не изменится в будущем.
Например, оптимизированный код Turbofan основан на предположении, что некоторое дополнение всегда добавляет целые числа.
Но что было бы, если бы он получил строку? Этот процесс называется деоптимизацией. Мы выбрасываем оптимизированный код, возвращаемся к интерпретируемому коду, возобновляем выполнение и обновляем информацию о типе.
Резюме
В этой статье мы обсудили реализацию JS-движка и точные этапы выполнения JavaScript.
Подводя итог, давайте посмотрим на конвейер компиляции сверху.
Мы рассмотрим это шаг за шагом:
Все начинается с получения кода JavaScript из сети.
V8 анализирует исходный код и превращает его в абстрактное синтаксическое дерево (AST).
На основе этого AST интерпретатор Ignition может начать делать свое дело и создавать байт-код.
В этот момент движок запускает код и собирает отзывы о типах.
Чтобы он работал быстрее, байтовый код может быть отправлен оптимизирующему компилятору вместе с данными обратной связи. Оптимизирующий компилятор делает на его основе определенные предположения, а затем создает высокооптимизированный машинный код.
Если в какой-то момент одно из предположений оказывается неверным, оптимизирующий компилятор деоптимизируется и возвращается к интерпретатору.
Блог · В8
Блог · V8 · V8
V8 Release V9.9 Release
Библиотека Oilpan Интернет-память CPPGC
V8 Release V9.7 Release
Webassembly Dynamic Многоуровневая версия готова к использованию в Chrome 96 WebAssembly
Версия 8 версии 9.6
Версия версии 8 версии 9.5
V8 Release V9.3 09 августа 2021 Release
V8 Release V9.2 16 июля 2021 Release
SparkPlug — неоптимизирующий компилятор JavaScript 27 мая 2021 JavaScript
короткие встроенные звонки 06 мая 2021 javaScript
V8 Release V9.1 04 мая 2021 Release
V8 Release V9.0 17 марта 2021 Release
более быстрые выпуски 04 марта 2021
Super Fast супер доступ к свойствам 18 февраля 2021 г. JavaScript
Более быстрые вызовы JavaScript 15 февраля 2021 г. внутренние компоненты
Версия V8 v8.9 04 февраля 2021 Release
Дополнительные не обратные Regeexp Engine 11 января 2021 Internal Regexp
V8 Relexexp
V8 Release V8.7 23 октября 2020 г. RELUMPLE
Индикация Tracer 01 октября 2020 года system-analyzer
Отслеживание резервов в V8 24 сентября 2020 г. Internals
Выпуск V8 v8.6 21 сентября 2020 г. выпуск
Выпуск V8 v8.5 21 июля 2020 г. выпуск v6 85 9000 30 июня 20209 выпуск
Высокопроизводительный сбор мусора для C ++ 26 мая 2020 20020 интернет-память CPPGC
Понимание ECMAScript Spec, часть 4 19 мая 20 мая Ecmascript Понимание Ecmascript
до 4 ГБ памяти в Webassembly 14 мая 2020 г. Инструмент WebAssembly JavaScript
Выпуск V8 v8.3 04 мая 2020 г. Выпуск
Что в этом .wasm ? Представляя: WASM-DECOMPILE 27 апреля 202061 27 апреля 20209 Webassembly Tooling
Понимание Ecmascript Spec, часть 3 01 апреля 202020 ECMAScript Понимание ECMAScript
Указатель сжатия в V8 30 марта 20209 Международная память
Понимание Ecmascript , часть 2 02 марта 2020 г. ECMAScript Понимание ECMAScript
Выпуск V8 v8.1 25 февраля 2020 Release
Понимание Ecmascript Спецификация, часть 1 03 февраля 2020 ECMAScript Понимание ECMAScript
V8 Release V8.0 18 декабря 2019 г. Release
За пределами Интернета: Автономные Webassembly Двухместный номер с использованием Emscripten 21 Ноябрь 2019 г. Инструмент WebAssembly
Выпуск V8 v7.9 20 ноября 2019 г. Выпуск
Улучшение регулярных выражений V8 04 октября 2019 г. Внутренние функции RegExp
Выпуск V8 v7.8 27 сентября 2019 Release
Зажигалка V8 12 сентября 2019 г. Интернет-памятью презентации памяти
История рекламного скала V8 в Reaction 28 августа 2019 года доклады
V8 Release V7.7 13 августа 2019 г. выпуск
Emscripten и серверная часть LLVM WebAssembly 01 июля 2019 г. Инструментарий WebAssembly
Стоимость JavaScript в 2019 г. 25 июня 2019 г.6 19 Июнь 2019 Release
Кеширование кода
Код Кэширование для Webassembly Developers 17 июня 2019 г. Webassembly Internals
V8 Release V7.5 16 мая 2019 г. Release
Быстрее и более богатые функционализация APIS 25 апреля 2019 г. Ecmascript INTL
в год с призраком: перспектива V8 23 апреля 2019 Безопасность
Глиняюще быстро разбираясь, часть 2: ленивый разборки 15 апреля 2019 интернет-аналогов
кэширования кода для разработчиков JavaScript 08 апреля 2019 г. Интернет
Молниеносно быстрый синтаксический анализ, часть 1: оптимизация сканера 25 марта 2019 г. разбор внутренних компонентов
Выпуск V8 v7.4 22 марта 2019 Release
JIT-менее V8 13 марта 2019 Internalsals
V8 Release V7.3 07 февраля 2019 Release
Мусорное ведение мусора: Коллектор мусора Ориноко 03 января 2019 г. Интернет-памятью презентации
Выпуск V8 v7.2 18 декабря 2018 г. Выпуск
Ускорение элементов распространения 04 декабря 2018 г.1 31 Октябрь 2018 Release
V8 Release V7.0
15 октября 2018 Release
Получение вещей сортировано в V8 28 сентября 2018 ECMAScript Internals
Улучшение DataView Выступление в V8 18 сентября 2018 ECMOScript
Празднование 10 лет V8 11 сентября 2018 г. 11 сентября 2018 Reftoff
Reftoff: новый базовый компилятор для Webassembly в V8 20 августа 2018 Webassembly Internals
Встроенные встроенные встроенные 14 августа 2018 года Internals
V8 Release V6.9 07 августа 2018 Release
V8 выпуск V6.8 21 июня 2018 Release
одновременная маркировка в V8 11 июня 2018 интернет-память
V8 Release V6.7 04 мая 2018 Release
To V8 02 мая 2018 ECMASCRIPS
ECMASSCRICE
Улучшенный код Caching 24 апреля 2018 Internals
V8 Release V6.6 27 марта 2018 Release
Фоновая компиляция 26 марта 2018 Internals
Отслеживание от JS до DOM и обратно 01 марта 2018 г. внутренняя память
Ленивая десериализация 12 февраля 2018 г. внутренняя часть
Выпуск V8 v6.5 01 февраля 2018 г. Выпуск
Оптимизация хэш-таблиц: скрытие хэш-кода 29 января 2018 г. Внутреннее устройство
Chrome приветствует Speedometer 2.0! 24 января 2018 тестов
Release
V8 Release V6.4 19 декабря 2017 г. RELELITE
JavaScript Coverage 13 декабря 2017 г. Интернет
Orinoco: молодое поколение в версии 8 — CodeStubAssembler 16 ноября 2017 г. внутренних компонентов
Объявление о тестировании веб-инструментов 06 ноября 2017 г. тестов Node.JS
V8 Release V6.3 25 октября 2017 Release
Оптимизация ES2015 Proxies в V8 05 октября 2017 ECMAScript Тема на лень
Стажировка на лень: ленивая безликерные функции 04 октябрь 2017 интернет-память
Временное отключение анализа выхода 22 сентября 2017 г. безопасность
Типы элементов в V8 12 сентября 2017 г. презентации внутренних компонентов
Версия V8 v6.2 11 сентября 2017 Release
Быстрая недвижимость в V8 30 августа 2017 г. Internals
О том, что хеш-наводнение уязвимость в Node.js … 11 августа 2017 г. Безопасность
V8 Release V6.1 03 августа 2017 Release
V8 Release V6.0 09 июня 2017 г. Release
Запуск зажигания и турбофана 15 мая 2017 г. Internals
V8 Release V5.9 27 апреля 2017 Release
Уход на пенсию 12 апреля 2017 г.
Версия V8 v5.8 20 марта 2017 Release
Fast для — в в V8 01 марта 2017 Интернет-интернал
Высокопроизводительные ES2015 и за пределы 17 февраля 2017 Ecmascript
Помогите нам проверить будущее V8! 14 февраля 2017 интернет-интернал
один маленький шаг для хрома, один гигантский куча для V8 09 февраля 2017 Memory
V8 Release V5.7 06 февраля 2017 Release
Ускорение регулярных выражений V8 10 января 2017 г. internals RegExp
Как V8 измеряет реальную производительность 21 декабря 2016 г. эталонные тесты
V8 ❤️ Node.JS 15 декабря 2016 Node.js
V8 Release V5.6
V8 Release V5.6 02 декабря 2016 Release
Browser Browser Release Preview 31 октября 2016 Webassembly
V8 Release V5.5 24 октября 2016 г. Release
Оптимизация V8 Потребление памяти 07 Октябрь 2016 Памяти памяти
V8 Release V5.4 09 Сентябрь 2016 Release
Выключение переводчика зажигания 23 августа 2016 Internals
V8 на конференции Blinkon 6 21 июля 2016 года презентаций
Версия V8 v5.3 18 июля 2016 Release
Release
V8 Release V5.2 04 июня 2016 выпуск
ES2015, ES2016, и за пределы 29 апреля 2016 ECMAScript
V8 Release V5.1 23 апреля 2016 Release
Jank Busters Часть двух: orinoco 12 апреля 2016 интернет-память
V8 Release V5.0 15 марта 2016 г. Release
Экспериментальная поддержка для Webassembly в V8 15 марта 2016 г. WebAssembly
Regexp Alloybehing Успокой на 26 февраля 2016 Ecmascript RegExp
Дополнительные компоненты V8 04 февраля 2016 г. Внутренние компоненты
Версия V8 v4.9 26 января 2016 Release
Там Math.random () , а потом Math.random () 17 декабря 2015 г. ECMAScript Internals
V8 Release V4.8 25 ноября 2015 выпуск
Jank Busters Part One 30 октября 2015 Memory
V8 Release V4.7 14 октября 2015 Release
Custom Startup Snapshots 25 сентября 2015 Internals
V8 Release V4.6 28 августа 2015 г. Release
бесплатная сборка мусора 07 августа 2015 г. внутренняя память
Кэширование кода 27 июля 2015 г. внутренняя часть
V8 выпуск v4.5 17 июля 2015 г. выпуск
Копание в JIT TurboFan 13 июля 2015 г. Внутреннее устройство
Привет, мир! 10 июля 2015 г.
Выпуск V8 v9.9 · V8
Каждые четыре недели мы создаем новую ветвь V8 в рамках процесса выпуска. Каждая версия является ответвлением от основного Git V8 непосредственно перед вехой Chrome Beta. Сегодня мы рады объявить о нашей новейшей ветке, V8 версии 9.9, которая находится в стадии бета-тестирования до ее выпуска в сотрудничестве с Chrome 99 Stable через несколько недель.V8 v9.9 наполнен всевозможными вкусностями для разработчиков. В этом посте представлен предварительный просмотр некоторых основных моментов в преддверии релиза.
JavaScript #
Intl.Locale extensions #
В v7.4 мы запустили Intl.Locale API. С V9.9 мы добавили семь новых свойств к объекту INTL.LOCALE объект: Calendars , Collaring , Часы , Numbersiansystems , Timezones , TextInfo и Weekinfo .
календари , сопоставления , hourCycles , numberingSystems и timeZones свойство Intl.Locale , предназначенное для использования с другими предпочтительными идентификаторами. API:
const arabicEgyptLocale = new Intl.Locale('ar-EG') arabicEgyptLocale.calendars arabicEgyptLocale.collations arabicEgyptLocale.hourCycles arabicEgyptLocale.numberingSystems arabicEgyptLocale.timeZones
Свойство textInfo свойства Intl.Locale возвращает объект для указания информации, относящейся к тексту. В настоящее время у него есть только одно свойство, direction , чтобы указать направление по умолчанию для текста в локали. Он предназначен для использования с атрибутом HTML dir и свойством CSS direction . Он указывает порядок символов — ltr (слева направо) или rtl (справа налево):
Свойство weekInfo свойства Intl.Locale возвращает объект для указания информации, относящейся к неделе. Свойство firstDay в возвращаемом объекте представляет собой число от 1 до 7, указывающее, какой день недели считается первым днем для целей календаря. 1 указывает понедельник, 2 — вторник, 3 — среду, 4 — четверг, 5 — пятницу, 6 — субботу и 7 — воскресенье.Свойство MinimumDays в возвращаемом объекте — это минимальное количество дней, необходимое в первую неделю месяца или года для целей календаря. Свойство week в возвращаемом объекте представляет собой массив целых чисел, обычно с двумя элементами, закодированный так же, как firstDay . Он указывает, какие дни недели считаются выходными для целей календаря. Обратите внимание, что количество выходных дней в каждой локали разное и может не совпадать.
arabicEgyptLocale.weekInfo
Intl Enumeration #
В v9.9 мы добавили новую функцию Intl.supportedValuesOf(code) , которая возвращает массив поддерживаемых идентификаторов в v8 для Intl API. Поддерживаемые значения кода : календарь , сопоставление , валюта , система нумерации , часовой пояс и единица измерения . Информация в этом новом методе предназначена для того, чтобы веб-разработчики могли легко определить, какое значение поддерживается реализацией.
INTL.SupportedValuesoF («календарь»)
INTL.SupportedValuesOf («сопоставление»)
INTL.SupportedValuesOF («валюта»)
INTL.SupportedValuesOf («Numbsingsystem»)
Intl.SupportedValuesof timeZone')
Intl.supportedValuesOf('unit')
API V8 #
Пожалуйста, используйте git log branch-heads/9.8..branch-heads/9.9 include/v8\6\*.h получить список изменений API.
Разработчики с активной проверкой V8 могут использовать git checkout -b 9.9 -t branch-heads/9.9 для экспериментов с новыми функциями в V8 v9.9. Кроме того, вы можете подписаться на бета-канал Chrome и вскоре опробовать новые функции самостоятельно.
Выпуск V8 v9.5 · V8
Каждые четыре недели мы создаем новую ветку V8 в рамках процесса выпуска. Каждая версия является ответвлением от мастера Git V8 непосредственно перед вехой Chrome Beta. Сегодня мы рады объявить о нашей новейшей ветке, V8 версии 9.5, которая находится в стадии бета-тестирования до ее выпуска в сотрудничестве с Chrome 95 Stable через несколько недель.V8 v9.5 наполнен всевозможными вкусностями для разработчиков. В этом посте представлен предварительный просмотр некоторых основных моментов в преддверии релиза.
JavaScript #
Intl.DisplayNames v2 #
В версии 8.1 мы запустили Intl.DisplayNames API API в Chrome 81 с поддерживаемыми типами «язык», «регион», «сценарий» и « валюта". В версии 9.5 мы добавили два новых поддерживаемых типа: «календарь» и «dateTimeField». Они возвращают отображаемые имена различных типов календарей и полей даты и времени соответственно:
const esCalendarNames = new Intl.DisplayNames(['es'], { тип: 'календарь' }); const frDateTimeFieldNames = new Intl.DisplayNames(['fr'], { type: 'dateTimeField' }); esCalendarNames.of('roc'); frDateTimeFieldNames.of('месяц');
Мы также улучшили поддержку типа «язык» с помощью новой опции languageDisplay, которая может быть либо «стандартной», либо «диалектной» (как значение по умолчанию, если не указано):
Intl.DateTimeFormat API в версии 9.5 теперь поддерживает четыре новых значения timeZone 0 optionName:
«shortGeneric» для вывода названия часового пояса в виде краткого общего формата без указания местоположения, такого как «PT», «ET», без указания перехода на летнее время.
«longGeneric» для вывода названия часового пояса в виде короткого общего формата без указания местоположения, например «Тихоокеанское время», «Горное время», без указания перехода на летнее время.
«shortOffset» для вывода названия часового пояса в сокращенном локализованном формате GMT, например «GMT-8».
«longOffset» для вывода названия часового пояса в длинном локализованном формате GMT, например «GMT-0800».
WebAssembly #
Обработка исключений #
V8 теперь поддерживает предложение WebAssembly Exception Handling (Wasm EH), так что модули, скомпилированные с помощью совместимой цепочки инструментов (например,грамм. Emscripten) может выполняться в V8. Предложение предназначено для снижения накладных расходов по сравнению с предыдущими обходными путями с использованием JavaScript.
Например, мы скомпилировали оптимизатор Binaryen в WebAssembly со старой и новой реализациями обработки исключений.
Когда обработка исключений включена, увеличение размера кода снижается примерно с 43 % для старой обработки исключений на основе JavaScript до всего 9 % для новой функции Wasm EH.
Когда мы запустили wasm-opt.wasm -O3 на нескольких больших тестовых файлах, версия Wasm EH не показала потери производительности по сравнению с базовой версией без исключений, в то время как версия EH на основе JavaScript заняла примерно на 30 % больше времени.
Однако Binaryen редко использует проверку исключений. Ожидается, что в рабочих нагрузках с большим количеством исключений разница в производительности будет еще больше.
V8 API #
Основной заголовочный файл v8.h разделен на несколько частей, которые можно включать отдельно. Например, v8-isolate.h теперь содержит класс v8::Isolate . Многие заголовочные файлы, объявляющие методы, передающие v8::Local, теперь могут импортировать v8-forward.h , чтобы получить определение v8::Local и всех типов объектов кучи v8.
Используйте git log branch-heads/9.4..branch-heads/9.5 include/v8\*.h , чтобы получить список изменений API.
Разработчики с активной проверкой V8 могут использовать git checkout -b 9.5 -t branch-heads/9.5 для экспериментов с новыми функциями в V8 v9.5. Кроме того, вы можете подписаться на бета-канал Chrome и вскоре опробовать новые функции самостоятельно.
Toyota поручает Yamaha Motor разработать водородный двигатель
A Yamaha Motor Co., водородный двигатель V8 представлен в Японии в субботу, 13 ноября 2021 г.
Тору Ханай | Блумберг | Getty Images
Toyota поручила Yamaha Motor разработать двигатель, работающий на водороде, причем президент последнего заявил, что его компания занимается разработкой двигателя внутреннего сгорания.
В объявлении, сделанном в конце прошлой недели, Yamaha заявила, что 5,0-литровый двигатель V8 будет разработан для автомобилей и основан на двигателе, используемом в купе Lexus RC F, с изменениями, среди прочего, в головках блока цилиндров и форсунках. вещи.
По заявлению Yamaha, агрегат способен выдавать до 450 лошадиных сил при 6800 оборотах в минуту. Компания заявила, что работает над водородным двигателем для автомобилей примерно пять лет.
Президент Yamaha Motor Ёсихиро Хидака сказал, что, хотя его компания стремилась достичь углеродной нейтральности к 2050 году, у нее также была «сильная страсть и уровень приверженности двигателю внутреннего сгорания».
«Водородные двигатели обладают потенциалом быть углеродно-нейтральными, в то же время сохраняя нашу страсть к двигателю внутреннего сгорания», — продолжил Хидака.
Заявление, сделанное на прошлой неделе, основано на заявлении от ноября 2021 года, когда Yamaha Motor, Kawasaki Heavy Industries, Toyota, Subaru и Mazda опубликовали подробности о том, что они назвали «проблемой расширения вариантов топлива для использования двигателей внутреннего сгорания». Именно на этом анонсе публике был показан двигатель V8, разработанный для Toyota.
Узнайте больше об электромобилях от CNBC Pro
Идея питания двигателя внутреннего сгорания водородом не нова.Toyota уже разработала GR Yaris с 1,6-литровым ДВС и использует водород в качестве топлива.
По данным компании, GR Yaris использует тот же силовой агрегат, что и Corolla Sport с водородным двигателем. Фирма назвала оба этих автомобиля «экспериментальными».
Такие фирмы, как BMW, также производили такие автомобили, как BMW Hydrogen 7. По словам немецкого автопроизводителя, Hydrogen 7 использовал двигатель внутреннего сгорания и мог работать на бензине или жидком водороде. Производство автомобиля началось в 2006 году и было выпущено ограниченным тиражом.
Использование водорода для питания двигателя внутреннего сгорания отличается от технологии водородных топливных элементов, при которой газ из баллона смешивается с кислородом, производя электричество. Как отмечает Центр данных по альтернативным видам топлива Министерства энергетики США, автомобили на топливных элементах выделяют «только водяной пар и теплый воздух».
Напротив, водородные ДВС производят выбросы. «Водородные двигатели выделяют почти нулевые следовые количества CO2… но могут производить оксиды азота или NOx», — говорит производитель двигателей Cummins.
Водородные ДВС также «менее эффективны» по сравнению с электромобилями на топливных элементах, по данным Центра данных по альтернативным видам топлива.
В то время как потенциал транспортных средств на водородных топливных элементах вызывает ажиотаж, и такие компании, как Hyundai, BMW и Toyota, разработали автомобили на основе этой технологии, другие представители отрасли придерживаются иного мнения.
В июне 2020 года генеральный директор Tesla Илон Маск написал в Твиттере: «Топливные элементы = дурацкие продажи», добавив в июле того же года: «Водородные дурацкие продажи не имеют смысла.
В феврале 2021 года генеральный директор немецкой Volkswagen Group также высказался по этому поводу. «Политикам пора принять науку», — написал в Твиттере Герберт Дисс. не оказаться в автомобилях. Слишком дорогой, неэффективный, медленный и сложный в развертывании и транспортировке. В конце концов: водородных автомобилей не предвидится».
Toyota и Yamaha объединяют усилия для разработки двигателя V8, работающего на водороде
В течение многих лет фирмы обещают поставлять водородные двигатели, утверждая, что водород — это чистое топливо будущего. .Однако мы мало что видим в плане развития.
Вскоре все может измениться.
Автопроизводители Toyota и Yamaha объединили усилия для разработки 5,0-литрового двигателя V8, работающего на водороде, говорится в заявлении, опубликованном Yamaha в этот четверг.
Водородный двигатель
Водородный двигатель не совсем нов. Он основан на модели, которая в настоящее время используется спортивным купе Lexus RC F.
Однако было сделано много модификаций, чтобы стать водородной версией с изменениями, касающимися форсунок, головок, впускного коллектора и других деталей.
Говорят, что двигатель развивает мощность 449 л.с. при 6800 об/мин и 398 фунт-фут при 3600 об/мин. Цифры немного меньше, чем у оригинального V8, на котором он основан, но для недавно разработанного экспериментального двигателя они более чем достаточны.
Дружелюбное отношение
И, по словам Такеши Ямады, инженера, работающего в команде разработчиков водородных двигателей Yamaha, этот двигатель обязательно сделает счастливым любого, кто на нем ездит.
«Водородные двигатели изначально дружелюбны, что делает их простыми в использовании, даже не прибегая к электронным системам помощи при вождении.Все, кто приезжал на тест-драйв прототипа автомобиля, начинали несколько скептически, но в конце выходили из машины с широкой улыбкой на лице», — говорится в заявлении Ямады. Engine следует отметить, что две компании ранее уже плодотворно сотрудничали. Они объединили усилия для создания 4,8-литрового двигателя V10 для гиперкара Lexus LFA мощностью 552 л.с., и это предприятие оказалось очень успешным.
Президент Yamaha Ёсихиро Хидака сказал: «Водородные двигатели обладают потенциалом быть углеродно-нейтральными, в то же время сохраняя нашу страсть к двигателю внутреннего сгорания».
Toyota и Yamaha работают над двигателем V8, работающим на водороде
Тест-драйв: Toyota GR 86 2022 года
Toyota GR 86 2022 года — это третье поколение удивительно выжившего спортивного автомобиля Редактор Fox News Autos Гэри Гастелу выводит его на трассу чтобы узнать почему.
NEWТеперь вы можете слушать статьи Fox News!
Yamaha и Toyota совместно работают над 5,0-литровым двигателем V8, работающим на водороде, который сможет обеспечить работу двигателя внутреннего сгорания на десятилетия вперед.
Yamaha разрабатывает двигатель V8, работающий на водороде.
(Yamaha)
Yamaha берет на себя инициативу по проекту, который может создать мощный поршневой двигатель без каких-либо выбросов выхлопных газов.
«Мы работаем над достижением углеродной нейтральности к 2050 году», — сказал президент Yamaha Motor Ёсихиро Хидака.«В то же время слово «Motor» присутствует в названии нашей компании, и, соответственно, у нас есть сильная страсть и уровень приверженности двигателю внутреннего сгорания».
Роскошный спортивный автомобиль Lexus LC является одним из автомобилей Toyota, в которых в настоящее время используется двигатель V8.
(Lexus)
Эти бренды вместе с Kawaskia, Subaru и Mazda в прошлом году объявили об инициативе по поиску углеродно-нейтральных альтернатив электрификации для автомобильного сектора. Сжигание водорода в основном производит водяной пар вместе с небольшим количеством оксидов азота, но не двуокиси углерода.
«Все, кто пришел на тест-драйв прототипа автомобиля, начинали с несколько скептического настроения, но в конце выходили из машины с широкой улыбкой на лице», — сказал член команды Такеши Ямада. «Когда я смотрел это, я начал верить, что на самом деле существует огромный потенциал в характеристиках, уникальных для водородных двигателей, а не просто рассматривать его как заменитель бензина».
Ямада сказал, что ключевой целью проекта является сохранение чувственных и волнующих характеристик, обеспечиваемых двигателем внутреннего сгорания, по сравнению с почти бесшумной работой электродвигателя.Двигатель имеет мощность 450 л.
V8 оснащен новым выпускным коллектором «8 в одном», который придает ему фирменный звук.
(Yamaha)
«Это вызов, в который мы можем погрузиться, как инженеры, и я лично хочу добиться не только производительности, но и нового очарования двигателя внутреннего сгорания, которого мир еще не видел», — сказал Ямада.
Другие автопроизводители, в том числе Porsche и Lamborghini, изучают способы создания двигателей внутреннего сгорания, которые могут соответствовать будущим экологическим нормам, включая использование синтетического топлива с нейтральным уровнем выбросов углерода. Проблема с водородом заключается в том, что процесс производства газа является энергоемким и часто производит большое количество углекислого газа, но разрабатываются новые методы, чтобы избежать этого.
Yamaha не сообщила, для какого автомобиля был предназначен V8, но единственные продукты Toyota, которые недавно использовали его, — это ее полноразмерные грузовики и модели Lexus с высокими характеристиками.
Toyota и Yamaha разработают водородный двигатель V8
История является доказательством того, что когда Toyota и Yamaha объединяются для создания двигателя, результат получается невероятно крутым. Lexus LFA V10 был совместной работой обоих, и это до сих пор, пожалуй, лучший серийный двигатель из когда-либо созданных. Сегодняшние новости касаются еще одного двигателя, разработанного Toyota и Yamaha, но работающего на водороде.
Да, это странно. Этот двигатель представляет собой 5,0-литровый V8, предназначенный для работы на водороде, а не на бензине.Двигатель, разрабатываемый Yamaha, начал свою жизнь как 5,0-литровый V8, используемый в таких автомобилях, как Lexus IS 500 F Sport Performance, RC F и других. Затем Yamaha вносит модификации в форсунки, головки цилиндров, впускной коллектор и многое другое. На данный момент Yamaha, похоже, не стремится раскрывать все свои секреты, но, по крайней мере, мы знаем, что это происходит.
Мощность этого водородного двигателя не так уж и далека от показателей, которых Lexus может достичь на старомодном бензине. При работе на водороде Yamaha заявляет о 455 лошадиных силах (при 6800 об/мин) и 398 Нм крутящего момента (при 3600 об/мин).
Этот проект согласуется с совместным заявлением Toyota, Yamaha, Kawasaki, Subaru и Mazda о том, что они «начнут обсуждение проведения совместных исследований возможных путей расширения диапазона вариантов топлива для двигателей внутреннего сгорания в поисках углеродной нейтральности. ”
«Водородные двигатели обладают потенциалом нейтрального выброса углерода, в то же время сохраняя нашу страсть к двигателю внутреннего сгорания», — сказал президент Yamaha Motor Ёсихиро Хидака.«Объединение с компаниями с разными корпоративными культурами и областями знаний, а также увеличение числа наших партнеров — вот то, как мы хотим проложить путь в будущее».
Компания Yamaha считает, что бензиновый двигатель с водородным двигателем может сохранить интересные детали бензинового двигателя — звук, диапазон мощности, ощущение — и все это при нейтральном уровне выбросов углерода. «Гармоничная высокочастотная нота выхлопа» из уникального коллектора 8-в-1 упоминается как одна из причин, по которой стоит волноваться по поводу будущего водородного двигателя V8.
«Я начал понимать, что двигатели, использующие в качестве топлива только водород, на самом деле обладают очень интересными и простыми в использовании рабочими характеристиками, — говорит Такеши Ямада из отдела автомобильных разработок Центра технических исследований и разработок Yamaha. «Водородные двигатели обладают естественным дружественным ощущением, что делает их простыми в использовании, даже не прибегая к электронным вспомогательным средствам вождения. Все, кто приезжал на тест-драйв прототипа автомобиля, начинали несколько скептически, но в конце выходили из машины с широкой улыбкой на лице.