[Перевод] Asm.js пришел в Chakra и Microsoft Edge
Несколько месяцев назад, мы объявили о начале работ по внедрению Asm.js. Поддержка Asm.js была одним из 10 наиболее востребованных запросов в на UserVoice для Microsoft Edge, начиная с самого запуска в декабре 2014 г. С тех пор мы добились хорошего прогресса: в Windows 10 Insider Preview, начиная со сборки 10074, вы можете попробовать Asm.js в Chakra и Microsoft Edge.
Что такое Asm.js? Asm.js — это строгое подмножество JavaScript, которое может быть использовано как низко-уровневый и эффективный язык для компилятора. Как подмножество asm.js описывает ограниченную виртуальную машину для языков с небезопасным доступом к памяти вроде C и C++. Комбинация статичной и динамичной проверок дает возможность движкам JavaScript использовать техники вроде специализированной компиляции без страховок или AOT-компиляции (Ahead-of-Time) для корректного asm.js-кода.
Подобные приемы помогают JavaScript выполняться с «предсказуемой» и «близкой к нативной» производительностью, причем оба свойства являются нетривиальными для достижения в рамках обычных оптимизаций компилятора для динамических языков вроде JavaScript.
Учитывая сложность написания asm.js-кода вручную, сегодня asm.js в основном производится за счет транскомпиляции C/C++ кода, используя такие инструменты, как Emscripten. Полученный результат используется в рамках веб-платформы вместе с такими технологиями, как WebGL и Web Audio. Игровые движки, например, Unity и Unreal, начинают внедрять раннюю или экспериментальную поддержку игр в вебе без использования плагинов, используя комбинацию asm.js и других связанных технологий.
Как я могу попробовать с Asm.js в Microsoft Edge? Чтобы включить поддержку Asm.js в Microsoft Edge, перейдите на страницу about: flags в Microsoft Edge и включите флаг «Enable asm.js», как показано ниже:
Встраивание Asm.js в поток исполнения кода Chakra Чтобы добавить Asm.js в Chakra, компоненты, выделенные ниже зеленым, были добавлены или изменены по сравнению с базовой моделью исполнения кода, описанной в статье про улучшения производительности JavaScript в Windows 10.Ключевые изменения:
Валидатор ASM.js, позволяющий Chakra обнаружить asm.js-код и проверить, что он соответствует спецификации asm.js. Генерация оптимизированного типо-специализированного кода. Учитывая, что asm.js поддерживает только нативные типы (int, double, float или SIMD-значения), Chakra использует информацию о типах для генерации типо-специализированного кода на основании кода на asm.js. Ускоренное выполнение типо-специализированного кода интерпретатором Chakra за счет использования информации о типах в целях исключения упаковки и распаковки числовых значений и благодаря исключению потребности в генерации профиля данных при интерпретации. Для не asm.js-кода на JavaScript интерпретатор Chakra создает и поддерживает профили данных для JIT-компилятора, чтобы обеспечить генерацию высоко-оптимизированного JIT-кода. Типо-специализированный байткод, созданный на основе корректного asm.js-кода, уже содержит всю информацию, необходимую для JIT-компилятора для создания оптимизированного машинного кода. Типо-специализированный интерпретатор работает в 4–5 раз быстрее чем интерпретатор с профилями данных. Ускоренная компиляция JIT-компилятором Chakra. Asm.js-код обычно генерируется с использованием LLVM-компилятора и инструментов Emscripten. JIT-компилятор Chakra учитывает уже сделанные оптимизации, присутствующие в asm.js-коде, сгенерированном LLVM-компилятором. К примеру, наш JIT-компилятор не делает прохода встраивания кода, ориентируясь на встраивание, уже сделанное LLMV-компилятором. Исключение анализа кода для таких JIT-оптимизаций не только помогает сэкономить циклы CPU и использование батареи при генерации кода, но также существенно повышает скорость работы JIT-компилятора. Предсказуемая производительность за счет исключения страховок в скомпилированном asm.js-коде. Учитывая динамическую природу JavaScript, все современные движки JavaScript поддерживают некоторую форму перехода к выполнению неоптимизированного кода, называемых страховками («bailout» в данном случае переведено как страховка). Они срабатывают в тем моменты, когда движки понимают, что предположения, сделанные JIT-компилятором, более не верны для скомпилированного кода. Страховки не только влияют на общую производительность, но и делают время выполнения непредсказуемым. Используя преимущества безопасных ограничений на типы для корректного asm.js-кода, код, сгенерированный JIT-компилятором Chakra, гарантированно не требует страховок. Оптимизации JIT-компилятора для Asm.js Помимо изменений в процесс исполнения кода, описанных выше, JIT-компилятор Chakra также пользуется преимуществами ограничений, накладываемых спецификацией asm.js. Эти улучшения помогают JIT-компилятору Chakra генерировать код, который будет выполняться с производительностью, близкой к нативной, — заветная цель для всех JIT-компиляторов динамичных языков. Давайте рассмотрим детальнее две такие оптимизации.Исключение вызовов помощников и страховок Код помощников и страховок, действует как мощный спасительный круг для скомпилированного кода динамических языков. Если любое из предположений, сделанных JIT-компилятором во время компиляции кода, становится неверным, должен быть безопасный механизм корректного продолжения выполнения кода.Помощники могут использоваться для обработки неожиданных ситуаций при выполнении операции, после чего контроль может быть возвращен JIT-коду. Страховки обрабатывают ситуации, в которых JIT-код не может восстановиться после наступления не предсказанных условий и управление требуется передать назад интерпретатору для выполнения оставшейся части текущей функции.
К примеру, если переменная кажется целым числом, и компилятор сгенерировал код с этим предположением, появление вещественного числа не должно влиять на корректность. В таких случаях код помощника или страховки используется для продолжения выполнения даже со снижением производительности. Для asm.js-кода Chakra не генерирует дополнительного кода помощников или страховок.
Чтобы понять, почему это не нужно, давайте посмотрим пример функции, возводящей в квадрат, внутри asm.js-модуля, который был упрощен в целях объяснения концепции:
function PhysicsEngine (global, foreign, heap) { «use asm»; … // Function Declaration function square (x) { x = +x; return +(x*x); } … } Функция square в примере кода выше транслирует две x64-машинные инструкции в код на asm.js, исключая пролог и эпилог, генерируемые для данной функции.
xmm1 = MOVAPS xmm0 //Перемещение нативного double во временный регистр xmm0 = MULSD xmm0, xmm1 //Умножение Для сравнения, скомпилированный код, сгенерированный для функции square, при отключенном asm.js включает около 10 машинных инструкций. В том числе:
Проверка, что параметр помечен как double, если нет, то переход к помощнику, конвертирующему из любого типа в double Извлечение вещественного значения из помеченного double Умножение значений Конвертация результата назад в помеченный double JIT-компилятор Chakra способен генерировать эффективный код для asm.js, используя преимущества информации о типах для переменных, которая не изменяется за время жизни программы. Валидатор и компоновщик Asm.js также это учитывают. Так как все внутренние переменные в asm.js имеют нативный тип (int, double, float или SIMD-значения), внутренние функции спокойно используют нативные типы без упаковки их в переменные JavaScript для передачи между функциями. В терминологии компилятора это часто называется прямыми вызовами и исключением упаковки или преобразований при работе с данными в коде на asm.js. При этом для внешних вызовов в или из JavaScript, все входящие переменные преобразуются в нативные типы, а исходящие нативные типы при упаковке преобразуются в переменные.
Исключение проверки границ при доступе к типизированным массивам В предыдущем посте мы рассказывали, как JIT-компилятор Chakra реализует оптимизацию автоматически типизированных массивов, одновременно вынося проверки границ за рамки массивов, таким образом улучшая производительность операций с массивом внутри цикла до 40%. Учитывая ограничения типов в asm.js, JIT-компилятор Chakra полностью исключает проверку границ для доступа к типизированным массивам для всего скомпилированного asm.js-кода, независимо от места в коде (внутри или снаружи цикла или функции) или типа типизированного массива. Он также исключает проверку границ для постоянных и непостоянных индексированных запросов к типизированным массивам. Вместе эти оптимизации позволяют достичь производительности, близкой к нативной, при работе с типизированными массивами в asm.js-коде.Ниже приведен упрощенный пример сохранения в типизированном массиве с постоянным сдвигом и одной x64-машинной инструкцией, сгенерированной компилятором Chakra для соответствующего кода на asm.js:
int8ArrayView[4] = 25; //JavaScript-код [Address] = MOV 25 //Одна машинная инструкция для хранения значения При компиляции asm.js-кода, JIT-компилятор Chakra может предварительно вычислить финальный адрес, соответствующий int8ArrayView[4], прямо во время компоновки asm.js-кода (первом вызове asm.js-модуля) и поэтому сгенерировать одну инструкцию, соответствующую сохранению в типизированном массиве. Для сравнения, скомпилированный код, сгенерированный для той же самой операции из не asm.js кода на JavaScript приводит к примерно 10–15 машинным инструкциям, что довольно-таки значительно. Операция при этом включает такие шаги:
Загрузка типизированного массива из переменной в замыкании (в asm.js типизированный массивы уже захватываются замыканием) Проверка, что переменная из замыкания на самом деле является типизированным массивом, если нет, то вызывается страховочный код Проверка, что индекс находится в рамках длины типизированного массива; если условия не выполняются, то происходит переход к коду помощника или страховки Сохранение значения в буфере массива Проверки, приведенные выше, удаляются из кода на asm.js благодаря строгим ограничения на то, что может быть изменено внутри asm.js-модуля. К примеру, переменная, указывающая на типизированный массив в asm.js-модуле не меняется. Если она изменяется, то это уже не корректный код на asm.js, что отлавливается на этапе валидации кода. В таких случаях код обрабатывается также, как и любая другая функция на JavaScript.
Улучшение сценариев и рост производительности от Asm.js Уже из первых результатов, которые вы можете попробовать в свежих сборка превью Windows 10, мы увидели несколько классных сценариев, выигрывающих в производительности от поддержки asm.js в Chakra и Microsoft Edge. Если вы хотите сами поиграться, запустите игры вроде Angry Bots, Survival Shooter, Tappy Chicken или попробуйте несколько веселых демок asm.js, приведенных здесь.[embedded content]
Если говорить про улучшения в производительности, есть несколько измерителей скорости asm.js, которые постоянно развиваются. Уже с предварительной поддержкой asm.js Chakra и Microsoft Edge работают более чем на 300% быстрее в Unity Benchmark и примерно на 200% быстрее в индивидуальных тестах вроде zlib, используемом в тестовых наборах Google Octane и Apple Jet Stream.
Unity Benchmark scores for 64-bit browsers (Click to enlarge)(System info: Intel® Core™ i7 CPU 860 @ 2.80GHz (4 cores), 12GB RAM running 64 bit Windows 10 Preview)(Scores have been scaled so that IE11 = 1.0, to fit onto a single chart)
Что же дальше? Это первый шаг в сторону интеграции asm.js в веб-платформу, стоящую за Microsoft Edge. Мы рады результатам, но еще не все сделали. Мы работаем над тонкой настройкой цепочки исполнения кода в Chakra для поддержки Asm.js — собираем данные, чтобы убедиться, что текущий подход к архитектуре работает хорошо на реальных сценариях использования asm.js, анализируем и пытаемся улучшить производительность, функциональность, поддержку инструментов и т.п. прежде, чем включать эту опцию по умолчанию.Как только она будет включена, asm.js заработает не только в Microsoft Edge, но также и станет доступен в Windows-приложениях, написанных на HTML/CSS/JS, а также в WebView, так как он использует движок рендеринга EdgeHTML, поставляемый с MicrosoftEdge в Windows 10.
Мы также хотим поблагодарить коллег из Mozilla, с которыми мы тесно сотрудничали с самого начала внедрения поддержки asm.js, и Unity за их поддержку и сотрудничество при внедрении asm.js в Chakra и Microsoft Edge. И отдельное большое спасибо всем разработчикам и пользователям Windows 10 и Microsoft Edge, которые оставляли нам ценные отзывы. Продолжайте в том же духе! Вы можете написать нам в Twitter на @MSEdgeDev или через Connect.