Pull to refresh

Высокопроизводительный GWT. Часть 1

Reading time6 min
Views11K
image
Данный пост является началом серии статей про оптимизацию и улучшение производительности GWT-приложений. Поскольку материала у меня накопилось достаточно много, решил разбить его на 2-3 части.
Приступим к описанию того, что нас ждёт в первой статье.

Рассматриваемые вопросы


В первой части цикла статей об оптимизации GWT-приложений я расскажу про следующие вещи:
  • Разделение клиентского кода, on-demand загрузка
  • Избавление от использования тяжелых классов на клиенте
  • Кэширование ресурсов
  • Проблемы производительности GWT RPC, использование REST
  • Особенности влияния layout на работу приложения
  • Оптимизация передаваемых данных
  • Использование Scheduler
  • Использование диспетчеров для агрегации запросов к серверу

А теперь подробно о каждом из упомянутых пунктов.

Разделение клиентского кода, on-demand загрузка


В GWT есть встроенный механизм, позволяющий разработчику «разрезать» приложение на части. Нужно это, в первую очередь, для следующих целей:
  • Уменьшение первоначального размера загружаемых данных
  • Использование принципа отложенной загрузки — необходимые данные будут загружены приложением по мере необходимости

Для того, чтобы разделить клиентский код на части, используется вызов метода GWT.runAsync.
Пример использования runAsync:

GWT.runAsync(new RunAsyncCallback() {
    public void onSuccess() {
      new MySettingsDialog().show();
    }
    public void onFailure(Throwable ohNoes) {
      // indicate that something went wrong,
      // usually a connectivity or server problem
    }
  });

При этом компилятор GWT берёт на себя обязанность не только произвести работу по разделению клиентского кода, но также проанализировать, как необходимо кластеризовать код. Гарантируется, что разделение будет корректным. Однако, разделения может и не произойти. Причиной этому могут быть перекрёстные ссылки в коде.
В презентации GWT Can Do What с Google I/O можно увидеть сравнение скорости загрузки приложения до и после разрезания его на части с использованием runAsync:



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

Избавление от использования тяжелых классов на клиенте


На странице группы Google Web Toolkit можно найти дискуссию о способах уменьшения размера компилируемого GWT-приложения. В частности отмечается подход, заключающийся в отказе от использования «тяжёлых» классов на стороне клиентов, влекущих включение большого количества кода на клиентскую часть.
В первую очередь, можно привести в пример использование Comparator на клиенте. Использование его влечёт за собой включение в клиентскую часть ~10 Кб кода связанных с ним классов.
Использование RequestFactory приводит к ещё большему увеличению клиента — в упомянутой выше дискуссии клиентская часть увеличилась на 150 Кб.
Стоит также быть внимательным в случае использования внешних модулей — вполне возможно, что их включение в проект повлечёт за собой существенное увеличение клиентской части за счёт добавления множества других классов.

Кэширование ресурсов


HTTP запросы со стороны клиента являются существенными ограничениями производительности клиентской части приложения. В случае, если приложение будет запрашивать отдельно каждый ресурс, плата за загрузку всех ресурсов будет большой.
Для предотвращения замедления клиентского приложения используется подход, связанный с кэшированием ресурсов.
В GWT есть механизм, названный ClientBundle. ClientBundle кэширует различные типы ресурсов (текстовые, графические, CSS и другие). Прочитать про ClientBundle можно по ссылке.
Преимущества подхода, применяемого в ClientBundle:
  • Необходимый ресурс будет загружен один раз вместо того, чтобы загружаться каждый раз по мере его использования
  • Суммарный объём хранимых ресурсов будет меньше

Как работает минимизация хранения ресурсов в случае ClientBundle?
Вернёмся к презентации GWT Can Do What. В ней мы можем увидеть следующую иллюстрацию:



Таким образом, уменьшается overhead для хранения однотипных данных.
Как в результате может уменьшиться объем ресурсов?



Проблемы производительности GWT RPC, использование REST


GWT RPC является действительно удобным средством. Обеспечивая интерфейс организации клиентского и серверного кода для сервиса, имея поддержку со стороны средств IDE, он является одной из удивительных особенностей построения AJAX-приложений в GWT. Однако стоит задуматься, подходит ли он вам.
Основными недостатки в случае использования GWT RPC могут служить добавление слишком большого количества избыточных данных в запрос и сложность отладки.
В качестве решения этих проблем можно отметить использование REST. В статье от Zack Grossbart можно найти пример использования REST для GWT-проектов.
При этом в его статье 4 More GWT Anti-patterns отмечаются основные преимущества REST:
  • REST вынуждает разработчика задумываться об API. API может также предоставляться внешним сервисам и приложениям, добавляя дополнительное удобство и функциональность для взаимодействия
  • Удобство тестирования
  • Чёткое разделение клиента и сервера

Особенности влияния layout на работу приложения


Очень часто gwt-разработчики используют автоподстраиваемый layout. Примерами тому может служить применение в компонентах height=«100%». Стоит сказать, что сontentHeight, offsetHeight — операции тяжеловесные. Ниже приведена статистика времени выполнения операции offsetHeight в IE, взятая из презентации Measure in Milliseconds с Google I/O 2009:



Мало того, что результат выполнения операции плохо предсказуем; его пиковое значение для 18 замеров достигло значение порядка 85 мс.
Определённое время занимает полная обработка resize event. Зачастую, операция изменения размеров в случае forced layout занимает время в несколько раз большее, чем style recalculation.
Как же можно избавиться от данной проблемы? Необходимо использовать CSS. Layout, определённый с помощью CSS, будет отрисовываться гораздо быстрее, лишив нас необходимости вызова для всей DOM-иерархии компонентов выполнение тяжеловесных методов, таких как offsetHeight.
Можно использовать обновлённый DockPanel, не использующий javaScript во время опрации resize.

Крайне не рекомендуется создавать и хранить большие деревья вложенных виджетов. Виджеты имеют дополнительный overhead, негативно сказывающийся на используемой памяти и размере клиента (как следствие, и на скорости). В качестве решения можно использовать HTMLPanel. Для отслеживания количества виджетов на страницах существует инструмент InspectorWidget. О свойствах плагина можно узнать на его странице.

Оптимизация передаваемых данных


Между клиентом и сервером постоянно передаются сериализуемые объекты. Разработчик должен спросить себя — действительно ли передаются лишь самые необходимые данные? Большие графы объектов, вложенные подобъекты и ненужные наборы параметров отнимают драгоценное время и создают лишнюю нагрузку. Передавайте лишь то, что нужно на клиенте. Зачастую — лишь то, что увидит пользователь. Всё необходимое можно будет получить в случае необходимости в данных. Разумеется, этот подход не должен противоречить политике кэширования ресурсов на клиентской стороне.

Использование Scheduler


В GWT есть механизм отложенных вызовов. Ранее он назывался DeferredCommand, но был объявлен deprecated. Теперь механизм носит название Scheduler.
Scheduler фактически предоставляет собой класс для асинхронного выполнения некого действия. Гарантируется, что действие будет выполнено после окончания event loop браузера.
Преимущества:
  • Не блокирует поток выполнения
  • Даёт возможность сначала завершиться всем UI событиям

Довольно часто Scheduler применяется при позднем построении некоторых объектов. Однако его грамотное использование будет в некоторых случаях делать UI приложения более быстрым за счёт больше приоритизации событий UI.

Использование диспетчеров для агрегации запросов к серверу


Каждый вызов к серверу накладывает некую задержку выполнения. Если мы имеем необходимость делать частые множественные вызовы к серверу, можно агрегировать их в некие batch-единицы.
Начиная с версии 2.3, в GWT появилась возможность с использованием RequestFactory:

requestContext.method1().to(new Receiver<T>(){...});
requestContext.method2().to(new Receiver<T>(){...});
requestContext.fire(new Receiver<Void>(){...}); //called only 1

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

В заключение


GWT — мощный инструмент, позволяющий создавать отличный кроссбраузерный интерфейс и широко применяемый уже и за пределами Google. Тем не менее, часть наиболее его удобных инструментов накладывают серьёзные ограничения на производительность приложения. Необходимо тщательно оптимизировать скорость работы приложения, чтобы пользователи также оставались довольны работой с ним.
В следующей статье я планирую продолжить рассказывать об особенностях улучшения производительности и оптимизации GWT-приложений. В частности, будут рассмотрены способы улучшения скорости работы GWT RPC, JS Shrink, особенности применения нативного кода на клиенте, советы для уменьшения размера скрипта; также планируется рассказать об оптимизации скорости компиляции и дать некоторые советы при вёрстке интерфейса.

Спасибо за внимание!

Полезные ресурсы:
  1. static.googleusercontent.com/external_content/untrusted_dlcp/www.google.com/en//events/io/2011/static/presofiles/drfibonacci_devtools_high_performance_gwt.pdf
  2. dl.google.com/io/2009/pres/W_1115_GWTCanDoWhat.pdf
  3. dl.google.com/io/2009/pres/W_1230_MeasureinMilliseconds-PerformanceTipsforGoogleWebToolkit.pdf
  4. turbomanage.wordpress.com/2010/07/12/caching-batching-dispatcher-for-gwt-dispatch
  5. www.zackgrossbart.com/hackito/antiptrn-gwt
  6. www.zackgrossbart.com/hackito/antiptrn-gwt2
  7. www.zackgrossbart.com/hackito/gwt-rest
  8. habrahabr.ru/blogs/gwt/99614
Tags:
Hubs:
Total votes 40: ↑37 and ↓3+34
Comments2

Articles