Я очень рад, что в Tapestry 5.1 произошли большие изменения: значительное увеличение производительности. Это может стать неожиданностью … что изменения производительности даже необходимы! Время отклика гобелена практически мгновенно для типичных приложений. Я действительно говорю об нетипичных приложениях … приложениях, которые раздвигают границы производительности и масштабируемости.
Это было обусловлено парой клиентов, которые создают большие приложения на Tapestry: как большое количество одновременно работающих пользователей, так и очень сложные страницы, содержащие сотни компонентов, которые из-за циклов рендерится тысячи раз.
Один конкретный клиент, Lithium , перешел от JSP к решению Tapestry 5 для своей сети сайтов сообщества. Они очень помогли с точки зрения отслеживания проблем с производительностью и решений.
Сделай так, чтоб это работало. Сделать это правильно. Сделай это быстро.
Tapestry 5 — это полное переписывание устаревшей кодовой базы Tapestry … хотя мне трудно думать о ней как о «новой», так как я работаю над ней с 2006 года! Тем не менее, первая часть Tapestry 5 состояла в том, чтобы заставить его работать, и Tapestry привел много инноваций здесь, особенно с точки зрения перезагрузки живого класса и нового контейнера Inversion of Control. К моменту финального релиза Tapestry была «правильной», и она была достаточно быстрой … но не было сделано никаких усилий, чтобы сделать это быстро. Tapestry 5.0 поддерживала хороший базовый дизайн и высокую скорость работы Java. Благодаря вкладу Lithium я работал над тем, чтобы реальный код соответствовал целям производительности.
Сравнение с JavaServer Pages
Сравнение производительности приложений Tapestry может оказаться непростым делом, поскольку использование памяти и шаблон обработки Tapestry полностью отличаются от простых операций, таких как сервлет или страница JavaServer Page (JSP). Одно препятствие, которое никогда не может быть преодолено, является промежуточным представлением: у JSP нет ни одного; первый битовый вывод будет передан клиенту, что очень эффективно.
Гобелен, напротив, фактически отображает шаблоны и компоненты в легкий DOM (объектная модель документа). Затем происходит некоторая постобработка DOM … добавлены новые элементы, связанные с JavaScript и другими функциями. Затем DOM отображается в поток символов. Гобелену всегда будет требоваться больше памяти для каждого запроса и больше времени, чтобы начать рендеринг контента.
Кроме того, страницы Tapestry являются полными объектами с состоянием, а не одноэлементными объектами, до которых компилируются JSP. Это также создает дополнительные накладные расходы: прежде всего создавать их, управлять (и сбрасывать) их внутренним состоянием и объединять их между запросами.
Интересно, что для небольших страниц Tapestry работает быстрее или быстрее, чем JSP. Вы можете понять, почему, если взглянуть на реализацию JSP Jasper: она тратит много времени на управление пулами экземпляров тегов JSP, постоянно проверяет их, настраивает их для одноразового использования, затем очищает и помещает обратно в пул. Tapestry использует гораздо более грубую политику кэширования (целые страницы, а не компоненты внутри страниц) и имеет больше возможностей для оптимизации потока данных на странице между компонентами.
Ускорение рендеринга страниц
Моим первым шагом в улучшении производительности была оптимизация конечного автомата рендеринга Tapestry. Tapestry разбивает рендеринг каждого отдельного компонента на ряд состояний:
Компонент может предоставлять метод фазы рендеринга для каждого состояния. Но большинство компонентов предоставляют методы только для одного или двух из этих этапов. Некоторые фазы имеют смысл только для компонентов с шаблонами (не для всех компонентов). Tapestry 5.0.18 перебирает каждое состояние для каждого компонента, независимо от … немного трэша в середине рендеринга. Анализируя, какие методы рендеринга фазы фактически реализует компонент, Tapestry 5.1 может оптимизировать большие фрагменты конечного автомата. Это приводит к значительно меньшему количеству операций рендеринга: меньше работы для достижения того же результата. В моем тестовом приложении количество операций рендеринга уменьшилось на 25-30%. Что еще более важно, в сочетании с несколькими точечными оптимизациями приложение Tapestry сократило расстояние до приложения JSP с точки зрения производительности,хотя у него было гораздо более сложное поведение.
Я на самом деле экспериментировал с рядом других опций, которые я не буду здесь подробно описывать. Важной частью было измерение производительности с использованием JMeter и YourKit. Я провел несколько дней, анализируя горячие точки и пробуя разные теории.
Ускорение сборки страниц
Это было гораздо более интересное изменение для реальных приложений и реальной разработки. В Tapestry страницы — это объекты с внутренним состоянием (не одиночные, как сервлеты и действия Struts); это означает, что нам нужно создать несколько экземпляров страниц, когда два или более запросов одновременно ссылаются на одну и ту же страницу.
Создание страницы, функция сервиса PageLoader, довольно сложно; он должен согласовывать содержимое шаблона страницы с компонентами страницы и их шаблонами; он должен сопоставлять атрибуты в шаблонах с параметрами компонентов, а также подключать их. Одна вещь, которую я заметил, это то, что было много дублирующих усилий, когда один и тот же компонент используется несколько раз, либо на одной странице, либо на нескольких страницах.
Мой подход заключался в том, чтобы разделить создание экземпляров страницы на две фазы: одноразовая фаза анализа, чтобы выяснить, что нужно сделать, чтобы фактически построить страницу, и вторая повторяемая фаза для выполнения построения. Цель здесь состояла в том, чтобы ограничить объем вычислений, необходимых при создании страницы (особенно во второй раз).
Конечный результат — много кода, подобного этому:
private void expansion(AssemblerContext context) { final ExpansionToken token = context.next(ExpansionToken.class); context.add(new PageAssemblyAction() { public void execute(PageAssembly pageAssembly) { ComponentResources resources = pageAssembly.activeElement.peek().getComponentResources(); RenderCommand command = elementFactory.newExpansionElement(resources, token); pageAssembly.addRenderCommand(command); } }); }
Это большой выигрыш для разработки в больших проектах со многими большими и сложными общими компонентами, сокращая время обновления после перехода на класс компонента Java.
Ускорение клиента
Улучшения на стороне сервера — это одно, но во многих случаях больший выигрыш — это оптимизация на стороне клиента. Веб-приложения — это не просто отдельные HTML-страницы: все JavaScript, таблицы стилей, изображения и другие ресурсы так же важны для восприятия производительности пользователем.
Tapestry 5.1 решает эту проблему двумя способами: управление версиями и сжатие.
Versioning
В Tapestry 5.0 активы classpath (хранящиеся в JAR) открываются через фильтр Tapestry. Эти активы заканчиваются URL-адресом, который включает номер версии, и предоставляются клиенту с заголовком истечения срока давности. Это означает, что клиентский веб-браузер будет агрессивно кэшировать файл. Это не помогает при первом посещении веб-сайта, но имеет большое значение при навигации по сайту или повторном посещении, поскольку большая часть того, что требуется браузеру для размещения контента на экране, уже будет присутствовать без HTTP-запрос.
Tapestry 5.1 расширяет это: ресурсы контекста (файлы, хранящиеся в контексте веб-приложения) теперь могут быть отображены с помощью альтернативного URL-адреса, который также содержит номер версии, определенный приложением, а также получить заголовок истекающего в далеком будущем.
компрессия
В Tapestry 5.1 также добавлено сжатие содержимого: если клиент поддерживает его, то обработанные страницы и статические ресурсы, размер которых превышает настраиваемый, могут быть отправлены клиенту в виде сжатого потока GZIP. Это
делает помощь с первым визитом в заявку.
Есть преимущество в том, чтобы позволить Гобелену делать сжатие; Tapestry будет кэшировать сжатый поток, так что последующие запросы (от других клиентов) на тот же контент получат сжатую версию, не оплачивая затраты на сжатие на стороне сервера. Более традиционный подход, использующий фильтр сервлетов, не позволяет определить, какой контент является динамическим, а какой статичным, поэтому он должен сжимать и повторно сжимать один и тот же контент вслепую.
Обратная совместимость
Главным достижением всех этих изменений является проблема совместимости: единственными вещами, которые изменились, были внутренние реализации и некоторые внутренние интерфейсы. Существующие приложения Tapestry могут обновляться и сразу же извлекать выгоду из всех изменений, самое большее путем перекомпиляции.
Часть моих целей для Tapestry — сделать так, чтобы ваше приложение начинало с малого и развивалось вместе с вами. Эти улучшения производительности не будут видны для небольшого внутреннего приложения или нишевого приложения … но как только ваше приложение станет успешным и будет расти по объему и популярности, необходимая вам производительность уже будет по умолчанию.