Изменения

Template Toolkit

13 130 байтов добавлено, 15:23, 28 августа 2009
Нет описания правки
Или же очередная серия «[[Заметки об идиотизме|Заметок об идиотизме]]».
Главные минусы TT — TT — во-первых, его производительность, а во-вторых, как это ни парадоксально — богатство возможностей, провоцирующее на создание сложных шаблонов и перенос в них половины логики приложения. В итоге идея о простых шаблонах, которые может редактировать даже дизайнер, накрывается медным тазом. Для примера достаточно взглянуть на шаблоны и код Bugzilla.
Производительность TT страдает в первую очередь от {{CPAN|Template::Stash}} (от англ. ''stash'' — тайник, «заначка») — зачем-то введённого отдельного уровня хранения данных — по сути, собственной реализации хеш-таблицы со множеством наворотов, проверок и вообще лишних действий, созданной, по всей видимости, для обеспечения нетривиальных возможностей вроде установки функции-getter’а вместо значение переменной шаблона (возможность, которой за весь опыт веб-разработки лично мне пользоваться не приходилось). В Stash при каждом получении значения какой-либо переменной передаётся ссылка на массив — путь к этой переменной, что как минимум сразу делает невозможным «компилированный» доступ к переменным.
Так как обычный Stash — просто кошмар неиллюзорный, а Template Toolkit был (да и до сих пор) весьма популярен, в районе года 2000 появилась его XS-версия (то есть, переписанная на C) — {{CPAN|Template::Stash::XS}}. После этого TT-шаблонам, всё-таки, полегчало. Но, во-первых, Stash никогда не был единственным замедляющим работу TT уровнем, во-вторых, даже Stash::XS всё равно работает не так быстро, как мог бы, а в-третьих, Stash::XS долгое время имел различные баги — например, поддержка UTF-8 в {{CPAN|Template::Stash::XS}} появилась только 4 июля 2009 года.
 
=== Пример: Bugzilla ===
 
[[lib:Bugzilla|Bugzilla]] — популярный и распространённый баг-трекер, «система контроля ошибок», разработанная организацией [http://www.mozilla.org/ Mozilla], написанный на Perl’е.
 
Изначально Bugzilla вообще не была веб-приложением, а работала на основе библиотеки [[rupedia:Tk|Tk]]. Однако это было давно и неправда и, как и распространённые версии 2.x, так и версии 3.x используют Template Toolkit.
 
Bugzilla 2, с точки зрения качества кода, вообще была очень сомнительным поделием, поддерживающим только запуск через CGI и содержащим большое количество старого кода, рассчитанного на то, что недоступны ни транзакции, ни даже библиотека Perl {{CPAN|DBI}}, файлы-вложения хранились в виде BLOB’ов в базе, интерфейс был весьма аскетичен и местами неудобен, и т. д. и т. п. Тем не менее, свои задача Bugzilla 2 выполняла хорошо, и почти никаких проблем с надёжностью не испытывала. Для генерации страниц Bugzilla 2 использует Template Toolkit и, несмотря на CGI, показывает приличную производительность (нет, конечно же не веб-2.0, но для баг-трекера вполне) благодаря относительной простоте кода.
 
Bugzilla 3 же показала большой прогресс в области удаления старого кода и написания нового. Появились транзакции, DBI, mod_perl, поддержка UTF-8, более приятный интерфейс, защита от дублирования изменений, для почты стали наконец-то использоваться нормальные (ну, ''такие же'') шаблоны для писем (это с большой оговоркой: если Template Toolkit вообще считать нормальным). Также авторы начали понимать, что Bugzillе совершенно необходимы возможности расширения и постепенно добавляют таковые — в 3.0 появляются Custom Fields (дополнительные поля у багов) и чуть-чуть хуков, в 3.2 настраиваемый жизненный цикл бага, поддержка расширений авторизации и ещё чуть-чуть хуков, и т. п.
 
Но есть и беда: Bugzilla всё ещё использует Template Toolkit, а разрабатывается длительное время и большим числом людей разной степени вшивости :) в итоге люди начинают использовать ''ну, прямо-таки все'' возможности Template Toolkit’а, что ведёт к уже упомянутому анти-паттерну разработки: перемещению 50 % логики в шаблоны.
 
Практически это приводит к следующим эффектам. Например, открытие бага с большим числом комментариев (например, с 703-емя — это наш максимум в [[http://www.custis.ru/ ЗИС]]'е) в Bugzilla 2.x, даже с учётом того, что это CGI-скрипт (то есть перезапускается интерпретатор), занимает примерно 1.5 секунды, а в 3.x, даже с учётом того, что это mod_perl (то есть интерпретатор не перезапускается и модули тоже не перезагружаются — ''хочется быстрее, да?'')… примерно 22 секунд, из которых 6 тратятся на работу {{CPAN|Template::Stash}}. После некоторых манипуляций средней хитрости время с 22 сек было доведено до 5.5 секунд, но это всё равно уже никак не 1.5 секунды 2-ой багзиллы, а для дальнейшего прогресса «локальных» улучшений уже мало — нужно глобально менять, так сказать, «архитектурные элементы». Та же история, хоть и менее катастрофическая, с поиском багов — было полсекунды, стало полторы. Некритично, конечно, но где же оно, заявленное «this greatly improves performance and highly decreases the memory footprint»?..
 
А в процессе манипуляций был выявлен замечательный идиотизм, который надо, во-первых, увековечить, а во-вторых, и зафиксить тоже не помешает. Код багзиллы, к счастью, доступен online на [http://mxr.mozilla.org/bugzilla/ Mozilla Cross-Referencer]. Мы будем рассматривать ветку [http://mxr.mozilla.org/bugzilla3.4/ Bugzilla 3.4] (вдруг потом баг всё-таки пофиксят). Но вообще он присутствует и в 3.2, и в 3.0.
 
Итак. В коде [http://mxr.mozilla.org/bugzilla3.4/source/Bugzilla/Template.pm Bugzilla::Template] есть такая замечательная функция, как quoteUrls, отвечающая за преобразование слов «bug XXXX» и «comment XX» в комментариях в ссылки на баги и оборачивание обычных ссылок и e-mail адресов в гипертекстовые ссылки. А в ней есть следующая строчка:
 
161 sub quoteUrls {
...
247 my $bug_word = get_text('term', { term => 'bug' });
 
Строчка относится к получению настоящего названия термина «баг» — в Bugzilla 3 можно баги называть не багами, а «задачами», «проблемами», «требованиями» и вообще как душе угодно. Функция quoteUrls при выводе страницы по разу вызывается для каждого комментария. А что делает get_text? Обратимся к модулю [http://mxr.mozilla.org/bugzilla3.4/source/Bugzilla/Util.pm Bugzilla::Util]:
 
597 sub get_text {
598 my ($name, $vars) = @_;
599 my $template = Bugzilla->template_inner;
600 $vars ||= {};
601 $vars->{'message'} = $name;
602 my $message;
603 $template->process('global/message.txt.tmpl', $vars, \$message)
604 || ThrowTemplateError($template->error());
605 # Remove the indenting that exists in messages.html.tmpl.
606 $message =~ s/^ //gm;
607 return $message;
608 }
 
А теперь заглянем в шаблон [http://mxr.mozilla.org/bugzilla3.4/source/template/en/default/global/message.txt.tmpl global/message.txt.tmpl] — он почти ничего не делает, но включает в себя другой шаблон: [http://mxr.mozilla.org/bugzilla/source/template/en/default/global/messages.html.tmpl global/messages.html.tmpl], отвечающий за вывод огромного числа различных информационных сообщений (между прочим, цепочкой ELSIF’ов вместо кучи блоков и именного PROCESS), и в частности — отдельным ELSIF’ом — вывод термина <code>[% terms.$term %]</code>, ради которого, собственно, и вызывается <code>get_text('term', { term => 'bug' })</code>. Иными словами, это 33 килобайта, выполняющиеся заново ''при обработке '''каждого''' коммента к багу''. В нашем случае — каждого из 703… На это уходило почти 10 секунд из всех 20-и, то есть почти 50 % времени.
 
Порассуждаем:
 
* Во-первых, функция get_text с первым аргументом 'term' вызывается в коде Bugzilla только в одном месте (см.выше), а все term’ы содержатся в отдельном шаблоне [http://mxr.mozilla.org/bugzilla3.4/source/template/en/default/global/variables.none.tmpl global/variables.none.tmpl]. Поэтому получение термина можно вынести в отдельную функцию.
* Во-вторых, нет никакой нужды выбирать нужную функцию в [http://mxr.mozilla.org/bugzilla/source/template/en/default/global/messages.html.tmpl global/messages.html.tmpl] цепочкой ELSIF’ов — можно применить блоки Template Toolkit’а, или даже просто отдельные шаблончики. Хотя самое забавное, что время-то как раз съедали не ELSIF’ы, а постоянные дёрганья [http://mxr.mozilla.org/bugzilla3.4/source/template/en/default/global/field-descs.none.tmpl global/field-descs.none.tmpl], представляющего собой по сути всего лишь пару вызовов {{CPAN|Template::Stash}}::set(), это очередная загадка TT.
* В-третьих, что самое главное — нет никакой нужды фильтровать тексты комментариев заново при каждом выводе страницы! Зачем выполнять лишнюю работу? Фильтрованный HTML-текст комментариев можно просто сохранить в базу данных рядом с нефильтрованным и спокойно выводить вообще без преобразования.
 
Вторая глобальная манипуляция — это именно разрешение {{CPAN|Template::Stash::XS}}, зачем-то специально запрещённого в коде Bugzilla 3.x. Наверное, это имело под собой какие-то основания, но когда баг открывается 20 секунд, все основания идут в задницу, и это позволило сэкономить ещё где-то 5 секунд.
 
Другие манипуляции были более мелкие — отказ от {{CPAN|Text::Wrap}} и оптимизация регулярных выражений в quoteUrls — и дали соответственный выигрыш: 1.5 секунды.
 
Чтобы не быть голословным, я даже приведу данные профилировщика. Использовался {{CPAN|Devel::NYTProf}}, написанный ребятами из [http://www.nytimes.com/ New York Times] (кстати, это очень хороший Perl-профилировщик, достойный отдельной статьи). Профилировалась Bugzilla не под mod_perl’ом, а под {{CPAN|HTTP::Server::Simple}} — проблем там никаких не возникает, потому что он тоже CGI (см. [[Платформы для запуска Perl веб-приложений]]). Всё тестировалось на «супер-баге» с 703 комментами.
 
* [http://vmx.yourcmc.ru/var/bugz3-profiling/000-bugzilla-2.x Bugzilla 2.x] (2.19.3).
* [http://vmx.yourcmc.ru/var/bugz3-profiling/000-bugzilla-3.x Bugzilla 3.x изначально] (3.4.1).
* [http://vmx.yourcmc.ru/var/bugz3-profiling/001-stash-xs Bugzilla 3.x + Template::Stash::XS].
* [http://vmx.yourcmc.ru/var/bugz3-profiling/002-get_term Bugzilla 3.x + оптимизация get_text('term')].
* [http://vmx.yourcmc.ru/var/bugz3-profiling/003-own-wrap Bugzilla 3.x + отказ от Text::Wrap].
* [http://vmx.yourcmc.ru/var/bugz3-profiling/004-quote-urls-optimize Bugzilla 3.x + оптимизация регэкспов в quoteUrls].
* [http://vmx.yourcmc.ru/var/bugz3-profiling/005-bugzilla3-query '''Неулучшенное''' — поиск багов в Bugzilla 3.x].
== Ссылки ==