Представляем Om
Не так давно, мы узнали, что все структуры данных в ClojureScript постоянные в отличие от оригинального Clojure, в котором они реализованные на Java. Сейчас JavaScript движки дошли до такого уровня, когда производительность коллекций стала в 2,5 раза выше, чем в JVМ.
Стоп, стоп, стоп. А какое же отношение может иметь производительность неизменяемых структур данных к JavaScript MVC? — Достаточно существенное.
Возможно, объяснить это будет не очень просто, но все же я постараюсь. Дело в том, что неизменяемые структуры данных, представленные в новой библиотеке Om позволяют создавать приложения на порядок производительнее, чем на популярных JS MVC фреймворков, таких как MVC Backbone.js (без ручной оптимизации). Om построен на прекрасном фреймворке от Facebook — React. Если вы не слышали о нём раньше, рекомендую посмотреть видео с JSConf EU 2013. Интересен тот факт, что из-за неизменяемых коллекций Om может продемонстрировать результаты лучше, чем при использовании React без каких-либо модификаций.
Бенчмарки, которые представлены далее, были созданы не для доказательства того, что Om – это быстрейшая библиотека в мире для построения UI. Они были созданы для демонстрации того, что очень часто принимаются решения, которые нельзя глобально оптимизировать. Также, современные фреймворки часто предоставляют небольшой объем документации и помощи, в связи с чем пользователи принимают решения, которые приводят к проблемам с производительностью.
Безусловно, проблемы в приложениях можно, утомительно, решать одну за другой. Но можно использовать фреймворки подобные Om, которые предоставляют комплексные уровни абстракции компонентов. Это поможет избавиться от целого ряда простых и трудоемких ручных методов оптимизации.
Игра бенчмарков
Откройте Om TodoMVC в браузере и запустите первый бенчмарк. Он создает 200 элементов списка, и на 11 дюймовом Macbook Air в браузере Safari 7 выполняется за 120ms.
Теперь откройте Backbone.js TodoMVC и запустите аналогичный бенчмарк. На моей машине страница отрендериловалось примерно за 500ms.
В Chrome и Firefox, на моей машине Om показывает результаты примерно в 2-4 раза быстрее чем Backbone.js.
Если вы попробуете переключить все элементы списка, Om сделает это моментально, тогда как при подобном действии Backbone.js продемонстрирует небольшое зависание.
Такая разница, вероятно, происходит из-за того, что Om перерисовывает страницу во время события requestAnimationFrame. Это позволяет существенно оптимизировать ваше приложение.
Давайте взглянем на профайлер в Chrome Dev Tools для этих бенчмарков. Мы можем увидеть большую разницу в работе React/Om из коробки по сравнению с не оптимизированным Backbone.js:
React/Om:
Backbone.js:
На мой взгляд, график React/Om показывает, что тут есть намного больше возможностей для глобальных оптимизаций и улучшений.
Хорошо, отличная работа! Но все же… Только сам факт увеличения производительности в 3 основных браузерах в 2-4 раза уже должен был заинтересовать многих. Особенно, учитывая то, что мы достигаем этого уровня благодаря неизменяемым структурам данных. Тут нет никакого увеличения в 30-40 раз, которое было сделано мной в Твиттере.
Теперь попробуйте второй Om бенчмарк – он создает 200 элементов, переключает их все 5 раз, а затем удаляет их. В Safari 7 на моём Макбуке это всё происходит примерно за 5ms.
Далее запустите Backbone.js. Убедитесь сначала, что удалили все элементы, а затем попробуйте второй бенчмарк. На моей машине в браузере Safari он выполнился за 4200ms.
Как такое возможно? — Просто.
Om практически никогда не выполняет ненужной работы. Данные, представления и логика контроллера не связаны между собой. При изменении данных, мы никогда сразу не ререндерим страницу. Мы просто откладываем перерисовку изменений до события requestAnimationFrame. По-сути, Om работает с браузером как с GPU.
Я предполагаю, что много JS MVC приложений повторяют архитектуру Backbone.js TodoMVC приложения, связывая вместе изменения модели и представления, а также других независимых частей, таких как сериализиация состояния приложения в LocalStorage. Всего лишь несколько фреймвоков предоставляют необходимую поддержку, для правильного построение архитектуры своих приложений. На самом деле, такое положение вещей не удивительно, поскольку большая часть фреймворков базируется на строковых шаблонах, CSS селекторах и прямом манипулировании с DOMом. Om оставляет позади все признаки place-oriented стиля программирования и другие потенциальные уязвимости в производительности.
Вы конечно же можете использовать Backbone.js или ваш любимый JS MVC фреймворк вместе с React, и это хорошее сочетание предоставляющее большую пользу. Однако, я не верю в событийно-ориентированные MVC-системы. И бенчмарки выше это подтверждают. Разделение моделей и представлений — это только первый важный шаг.
Надеюсь, это даст поводы для размышлений фанатам текущих JS MVC фреймворков, и даже людям, которые верят в использование только нативного JavaScript и jQuery. Выше я постарался показать, что компилируемый в JavaScript язык, который использует более медленные структуры данных, в конечном счёте быстрее чем его конкурент для построения более сложных интерфейсов. На верхушке списка лучших результатов находится Om TodoMVC с той-же функциональностью, как и все остальные фреймворки, объемом ~260 строк кода(включая все шаблонны) и уменьшенным весом 63K gzipped (это всё включает в себя: React — 27K, стандартную библиотеку ClojureScript, core.async, routing library, и несколько хелперов от Google Closure).
Если вы JavaScript разработчик, я считаю вам стоит хорошенько посмотреть на React. Я думаю в будущем связь React с библиотекой, которая обеспечивает использование стойких структур данных, например mori, могут привести JavaScript приложения к гибкой и очень хорошо настраиваемой архитектуре, которую предоставляет Om. Не смотря на то, что неизменяемые структуры данных генерируют больше мусора, мы все же надеемся, что современные разработчики справятся с этой проблемой. Также производительность устройств, которые мы носим в своих карманах, постоянно улучшается.
Технические описание следует далее.
Как это работает
Изменения и запросы к DOM дереву являются большой уязвимостью для производительности, и в React используется подход, который обходит эти места жертвуя экспрессивностью. Он предоставляет хорошо спроектированный объектно-ориентированный интерфейс, но если заглянуть под капот, мы увидим исходники, которые были созданы с точки зрения прагматичности функционального программиста. Он генерирует виртуальные версии DOM дерева, и как только в вашем приложении происходят изменения, то Om помечает изменения между виртуальными версиями DOM дерева через промежутки времени. Оно используется для получения минимального количества изменений, которое необходимо для настоящего DOM дерева.
В React существует достаточно важная функция shouldComponentUpdate, которая используется при вычислении разницы в виртуальном DOM, представленном вашими компонентами. Если она возвращает значение false, то React никогда не будет вычислять дочерних элементов этого компонента. Таким образом, React постепенно строит виртуальное DOM дерево, для получения данных о тех элементах, которые нужно изменить.
Как выяснилось, реализация shouldComponentUpdate по умолчанию слишком консервативна, потому как JavaScript разработчики как правило изменяют объекты и массивы. Поэтому для определения свойств компонента которые изменились, требуется вручную проходить по объектам и массивам.
Вместо использования JavaScript объектов, Om использует структуры данных из ClojureScript, которые, как мы знаем, не могут быть изменены. Исходя из этого, мы можем предоставить компонент, который реализует shouldComponentUpdate, с помощью использования наиболее быстрой проверки — на равенство ссылок. Это означает что мы можем всегда определить изменился в структуре за логарифмическое время.
Поэтому, нам не требуются операции на подобие setState, использующиеся для поддержки эффективных обновлений узлов дерева, а также хорошего объектно-ориентированного стиля. Обновление узлов дерева в Om, начиная с верхней точки всегда происходит быстрее, потому что мы только сравниваем ссылки по пути вниз.
Также, потому что мы всегда перерисовываем от верхней точки дерева, совмещённые обновления тривиальны для реализации. Мы не беспокоимся о поддержке совмещенных обновлений с React, так как он разработан чтобы обрабатывать за нас эти случаи.
И наконец, так как у нас всегда есть начальное состояние UI для каждой части данных, то ты можем просто сериализовать все важные состояния приложения. Больше не нужно беспокоится о протоколе сериализации, поскольку состояния Om UI всегда являются сериализуемыми.
Это также значит, что Om UI возвращает состояние мгновенно. Вы можете легко сохранить любое состояние в памяти и можете вернутся к нему, когда захотите. Это эффективное использование памяти, так как структуры данных из ClojureScript реализованы с помощью разделения памяти для них.
Завершающие мысли
В заключение, я не думаю что у текущего поколения JavaScript MVC фреймворков есть много перспектив в будущем. Я считаю, что если посидеть и поразмыслить над этим, то вы получите что-то нечто похожее на React/Om. Это сможет обеспечить оптимальный баланс между простотой, производительность и экспрессивностью. Тут нет ничего того, чего не было б известно раньше… Если вы станете воспринимать браузер как средство удаленной отрисовки страниц и перестанете хранить там всякий мусор, то все будет работать гораздо быстрее. Звучит как что-то знакомое, не так ли? Да, как программирование компьютерной графики.
ссылка на оригинал статьи http://habrahabr.ru/post/208978/
Добавить комментарий