Поэкспериментируйте С Двоичным Кодом В Glimmer.

Перевод статьи о экспериментирую с двоичным кодом в Glimmer , соавторы публикации: Сара Клаттербак, Чад Хиетала и Том Дейл.

Чуть больше года назад Эмбер.

js претерпел существенные изменения.

В тесном сотрудничестве инженеров LinkedIn и сообщества Open Source мы заменили механизм рендеринга Ember новой библиотекой Glimmer VM, которая повысила производительность и значительно уменьшила размер скомпилированных шаблонов.

Glimmer рассматривает шаблоны Handlebars как функциональный язык программирования и компилирует их в последовательность инструкций, которые можно выполнять в браузере.

Эти инструкции или коды операций закодированы в компактную структуру данных в форме JSON. Когда мы перенесли наше веб-приложение linkedin.com в Glimmer, мы увидели значительное улучшение времени загрузки.

Помимо уменьшения размера файлов на 40 %, мы также сократили время, необходимое браузеру для анализа JavaScript, путем компиляции шаблонов в JSON. Более того, это изменение улучшило время загрузки в 90% случаев более чем на 1 секунду.

В этой статье мы обсудим недавний эксперимент по дальнейшему сокращению времени загрузки за счет полного устранения времени, необходимого для анализа скомпилированных шаблонов.



Обнаружен эксперимент Glimmer.js

Около шести месяцев назад команда Ember.js объявила о выпуске Glimmer.js как отдельной библиотеки компонентов.

Отделение уровня представления позволило нам взять лучшее от Ember и виртуальной машины Glimmer и предоставить его разработчикам, которые создают легкие продукты, такие как мобильные приложения для развивающихся рынков или SEO-страницы.

Прорыв Глиммера позволил нашей команде провести множество экспериментов в последующие месяцы.

Недавно, например, мы представили гибридный рендеринг, при котором html генерируется на сервере, а затем регидратируется (см.

примечание переводчика).

Здесь ) в браузере.

Это только начало преимуществ в производительности, обеспечиваемых архитектурой виртуальных машин Glimmer. Святым Граалем веб-производительности является возможность быстрой первоначальной загрузки, быстрого обновления при выполнении действий пользователем (поддержание производительности 60 кадров в секунду) и обеспечения производительности по умолчанию.

Это означает, что большие команды с менее опытными разработчиками могут создавать эффективные веб-приложения без значительного вмешательства.

Традиционно существовала дилемма между предоставлением минимального количества кода JavaScript для мгновенной загрузки и возможностью иметь сложный, отзывчивый пользовательский интерфейс.

Фундаментальный компромисс, по-видимому, заключается в том, что по мере увеличения размера приложения производительность и производительность снижаются.

С помощью Glimmer наша цель — создавать легкие, быстрые и продуктивные приложения.

Одним из ключей к достижению этой цели является снижение стоимости каждого нового компонента, добавляемого в приложение.



Мгновенные шаблоны

Переход с JavaScript на JSON снижает затраты на анализ скомпилированных шаблонов, и мы объединили Glimmer с расширенными функциями браузера, чтобы полностью исключить этап анализа.

При оптимизации времени загрузки большинство разработчиков стараются уменьшить размеры файлов, чтобы ускорить загрузку.

Но в приложениях на основе JavaScript на производительность запуска также влияет способность браузера анализировать, компилировать и оценивать ваш код. Что немаловажно, поскольку на мобильных устройствах анализ и компиляция кода JavaScript происходит в 2-5 раз медленнее, чем на настольных компьютерах.

Один только этот шаг может оказать существенное влияние на общую производительность приложения.

Сегодня большинство фреймворков компилируют представление в функции JavaScript. Накладные расходы на анализ такого кода JavaScript часто скрыты, и по мере добавления новых функций приложение становится все медленнее и медленнее.

Как упоминалось выше, Glimmer компилирует шаблоны в последовательность кодов операций, которые передаются браузеру в виде JSON. В связи с тем, что грамматика JSON намного проще грамматики JavaScript, парсер JSON может работать в 10 раз быстрее, чем парсер JavaScript при анализе тех же данных.

Но это по-прежнему означает, что время анализа будет увеличиваться по мере увеличения размера шаблона, хотя и более медленными темпами.

Что, если бы мы могли вообще обойти этап синтаксического анализа? В последние годы браузеры стали лучше обрабатывать двоичные данные.

Используя низкоуровневый API, такой как ArrayBuffer, программы JavaScript могут обрабатывать двоичные данные так же быстро, как и их собственные аналоги.

Мы воспользовались этим, чтобы скомпилировать шаблоны в наш собственный формат байт-кода, который виртуальная машина Glimmer может выполнять напрямую.

Похож на формат Байт-код JVM Байт-код Glimmer — это независимый от платформы двоичный формат, который кодирует набор инструкций виртуальной машины Glimmer в поток байтов, состоящий из кодов операций и их операторов.

Вместо того, чтобы быть ограниченными производительностью синтаксического анализа JSON или JavaScript, мы теперь ограничены только способностью браузера копировать необработанные байты из Интернета.



Кодирование байт-кода Glimmer

Как и во многих виртуальных машинах, инструкции в виртуальной машине Glimmer распознаются по номерам.

байт-код — это просто закодированная последовательность этих чисел.

Уникальность Glimmer заключается в том, что его набор команд предназначен для рендеринга DOM в браузере.

Например, шаблон

  
  
  
  




<h1>Hello World</h1>

будет скомпилирован в следующий формат JSON во время сборки:

[ ["open-element", "h1", []], ["text", "Hello World"], ["close-element"] ]

В браузере последним шагом компиляции является преобразование формата JSON в массив чисел, где каждое число представляет код операции или операнд:

const Program = [25, 1, 0, 0, 22, 2, 0, 0, 32, 0, 0, 0];

Обратите внимание, что строки в нашем JSON заменены целыми числами.

Это связано с тем, что мы используем так называемую технику «интернирования строк», которая позволяет избавиться от дублирования одинаковых строк; здесь строки заменяются смещением в пуле строковых констант, что на практике существенно уменьшает размер файлов (только представьте, сколько раз вы повторяете div строковой константы в своих шаблонах).

Наш байт-код изначально кодировал каждую операцию как четыре 32-битных целых числа, где первое 32-битное целое число описывало тип операции (код операции), а остальные 96 бит описывали до трех аргументов инструкции (операндов).

Хотя этот подход эффективен для выполнения кода, недостатком является то, что файлы байт-кода больше, чем необходимо.

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

Кроме того, набор инструкций Glimmer содержит только 80 кодов операций, поэтому мы можем уменьшить зарезервированное пространство для кодов операций до 8 бит. В конечном итоге мы остановились на более компактной схеме кодирования, которая по-прежнему была 16-битной.

Первые 8 бит — это код операции, следующие 2 бита используются для кодирования количества операндов, а последние 6 бит зарезервированы для использования в будущем.

Каждый операнд, если он присутствует, кодируется дополнительными 16 битами.

При такой схеме кодирования каждая инструкция может занимать от двух до шести байт, выглядит это примерно так:

/* Fixed Opcode */ /* Operand? */ /* Operand? */ /* Operand? */ [0bIIIIIIIILLRRRRRR, 0bAAAAAAAAAAAAAAAA, 0bAAAAAAAAAAAAAAAA, 0bAAAAAAAAAAAAAAAA] /* I = instruction (opcode) type L = operand length R = reserved A = operand value */ view raw

Эта новая схема уменьшает размер скомпилированной программы на 50%.

«Декодирование» этой схемы требует незначительных накладных расходов, поскольку мы просто маскируем и сдвигаем биты, чтобы определить длину кода операции и длину операнда.







Преодоление разрыва между байт-кодом и JavaScript



Одной из проблем, с которыми мы столкнулись, был перенос всей фазы компиляции на сборщик проекта.

Ранее мы выполняли последний этап компиляции шаблона в браузере после загрузки кода JavaScript приложения.

Это позволило нам связать скомпилированные шаблоны с объектами JavaScript, такими как классы компонентов, которые обрабатывали действия пользователя.

Первым шагом было убедиться, что все уровни компиляции были в Node.js. Мы создали новый интерфейс под названием «компилятор пакета», который инкапсулировал все уровни компиляции в единый API, позволяя инструментам сборки превращать «набор» шаблонов в байт-код. Затем мы столкнулись с дополнительной проблемой: как при компиляции в байт-код «подключить» этот байт-код обратно к нужным объектам JavaScript во время выполнения? Для решения этой проблемы мы ввели понятие «ручки».

Обработчик — это уникальный числовой идентификатор, связанный с внешними объектами в шаблоне, такими как компоненты или помощники.

Во время компиляции мы связываем каждый внешний объект с обработчиком, который закодирован в байт-код. Например, если мы видим вызов компонент, мы можем связать его с обработчиком с идентификатором 42 (при условии, что уже был вызван 41 уникальный компонент).

Подобный вызов компонента компилируется в несколько кодов операций в наборе команд Glimmer. Одна из этих инструкций — 0x003b PushComponentDefinition, которая помещает класс компонента JavaScript в стек виртуальной машины (ВМ).

При компиляции в байт-код эта инструкция создаст четыре байта: 0x00 0x3b 0x01 0x2A. Первые два байта кодируют код операции PushComponentDefinition. Вторые два байта кодируют операнд, которым в данном случае является обработчик (число 42).

Так что же происходит, когда мы запускаем байт-код в браузере? Как превратить целое число 42 в живой, дышащий класс JavaScript? Мы называем этот трюк «таблицей внешних модулей».

Это небольшой фрагмент сгенерированного кода JavaScript, который соединяет два мира, определяя структуру данных, позволяющую эффективно сопоставлять обработчики с соответствующими классами JavaScript. В нашем примере мы связали UserProfile с обработчиком 42, поэтому наша таблица плагинов представляет собой массив, где класс UserProfile является 42-м элементом массива.



import UserProfile from '.

/src/ui/components/UserProfile/component'; /* .

other component imports */ export let table = [ /* Component1 */, /* Component2 */, /* .

*/, /* Component41 */, UserProfile, /* Component43 */, /* .

*/ ];

Во время выполнения байт-кода вспомогательный объект, называемый «преобразователем», превращает обработчик в соответствующий объект JavaScript. Поскольку каждый обработчик также является смещением в массиве, этот код прост и быстр:

resolve<U>(handle: number): U { return this.table[handle]; }



Поэкспериментируйте с двоичным кодом в Glimmer.

Соберите проект в формате .

gbx (Glimmer Binary Experience), перенесите его в браузер, и виртуальная машина отобразит заголовок в браузере.







Что дальше



Мы просто интегрировали компилятор байт-кода во внутреннее тестовое приложение Glimmer.js и надеемся вскоре получить реальные результаты в рабочем приложении.

Это поможет нам оценить влияние этих изменений на реальный мир с участием разных участников, использующих разное оборудование, операционные системы, браузеры и комбинации пропускной способности.

Поскольку байт-код Glimmer уменьшает размер файла и полностью исключает последний этап анализа и компиляции, мы ожидаем значительного улучшения времени запуска приложений, особенно на устройствах со слабыми аппаратными возможностями, где центральный процессор является узким местом.

Возможно, что еще более важно, процесс согласования формата файла и внутреннего устройства виртуальной машины в сторону четко определенного двоичного формата открывает множество интересных экспериментов в будущем.

В частности, выбранный нами формат байт-кода означает, что мы имеем хорошие возможности для исследования и модификации частей виртуальной машины Glimmer в соответствии с технологией WebAssembly, что снижает затраты на синтаксический анализ и еще больше повышает производительность во время выполнения.

Все мы в LinkedIn большие поклонники открытого исходного кода, и вся описанная выше работа была с открытым исходным кодом на GitHub. Если нас заинтересовал проект Glimmer, приглашаем вас в репозиторий.

Глиммер ВМ И Глиммер.

js на Гитхабе.







Благодарности



Большое спасибо Чад Хиетале И Том Дейл , который взял на себя задачу компиляции байт-кода в LinkedIn. Более того, благодаря Иегуда Кац И Годфри Ченг за помощь в реализации этого видения в сообществе открытого исходного кода.

Теги: #JavaScript #шаблоны #json #JavaScript #оптимизация клиента

Вместе с данным постом часто просматривают: