Asm.js в 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, приведенных здесь.



Если говорить про улучшения в производительности, есть несколько измерителей скорости 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(TM) 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.

Комментарии (18)


  1. olegkrasnov
    15.05.2015 12:57
    -25

    <offtopic>Сделать нормальный кроссплатформенный браузер у ребят кишка тонка. Edge даже под Windows 8.1 не заточен, не говоря уже о других ОС. Какой от него прок? Такая же бесполезная хрень, как Safari.</offtopic>


  1. olegkrasnov
    15.05.2015 13:06
    -18

    Извините, психанул.


  1. WST
    15.05.2015 14:00
    +11


  1. Z2spire
    15.05.2015 14:02
    +12

    Поддержка Asm.js была одним из 10 наиболее востребованных запросов

    если бы они не удаляли самые популярные запросы, то в 10 он бы не вошел.


    1. ReklatsMasters
      15.05.2015 19:12
      +5

      А какие запросы они удалили?


      1. Athari
        16.05.2015 01:28

        Вестимо, «Сожгите напалмом все компьютеры, на которых разрабатывался IE, сбросьте сверху ядерную бомбу, чтобы наверняка, и приступайте к развитию великого и вездесущего Blink, а то гореть вам в аду!!!!!!111адынадын», тысячи их.


    1. kaatula
      17.05.2015 07:18

      Присоединяюсь к вопросу
      Какие запросы были удалены? И как это произошло?


  1. Zibx
    15.05.2015 14:42
    +4

    Это очень хорошо, при таком раскладе в v8 тоже скорее всего запилят asm.js и мы получим возможность выжать дикой производительности из node.js.


    1. ReklatsMasters
      15.05.2015 19:14

      Где-то писали, что v8 не хотят делать поддержку asm.js из-за того, что у последнего нет законченной спеки.


      1. gonzazoid
        15.05.2015 21:51

        да вроде здесь же, на хабре, было. Только не из-за спеки, а, типа, если это строгое подмножество, то оно и без танцев с бубнами должно работать.


      1. kaatula
        17.05.2015 07:22

        Пойнтеры тоже не хотели. Когда-то.


    1. Antelle
      15.05.2015 20:42

      А зачем ноде asm.js? Можно сразу на C++ аддон скомпилить.


  1. some_x
    15.05.2015 14:45
    +3

    Я так понимаю в перспективе языки вроде Typescript научаться компилироваться в asm.js, что станет их дополнительным преимуществом.


    1. kichik Автор
      15.05.2015 14:52

      Конкретно у asm.js сейчас несколько другая ниша, но в целом любая предсказуемость поведения программы — это возможность снять дополнительные страховки в компиляторе.


    1. laticq
      18.05.2015 13:29

      Тоже об этом подумал, это было бы очень здорово.


    1. resnyanskiy
      05.06.2015 12:20

      github.com/Microsoft/TypeScript/issues/375
      Старая тема, еще с 2013 года фич-реквест. Если коротко: у TypeScript есть «идеологические несовместимости» с asm.js.


  1. nazarpc
    16.05.2015 07:02
    +2

    Прикольно что вы сравниваете с IE11, а слабо с Nightly версией Firefox или Chromium? Понятно что стало быстрее, но это же всё относительно, дайте конкретные попугаи на конкретном железе, а люди сами сделают вывод.

    Странно что компания, которая поддерживает браузер только для одной ОС, да ещё и ОС сама пилит, то есть контролирует весь стек технологий, получает результат, который проигрывает Firefox и Chromium. При этом последние работают на огромном зоопарке различных ОС и имеют многие фичи, которые здесь только планируются.


    1. kichik Автор
      16.05.2015 16:44

      Я думаю, самое правильно для вас — взять и сравнить самостоятельно, потому что разное железо, более свежие сборки и т.п.

      Вот сравнения от команды Edge: channel9.msdn.com/Events/WebPlatformSummit/2015/Chakra-The-JavaScript-Engine-that-powers-Microsoft-Edge (24:45).



      Ключевой вывод: преодолели отставание IE.