Оптимизации в Pyramid [перевод]

Блог им. bismigalis 17 мая 2013 г., 18:46

Ещё одна напыщенная и корыстная заметка о веб-фрейворке Pyramid; на этот раз о дизайне и реализации решений, связанных с оптимизацией.

Если вы посмотрите на svn.repoze.org/whatsitdoing/, то увидите измерения профайлингов для большого числа веб-фреймворков, включая Pyramid. «Ложь и бенчмарки» присутствуют, но относительные “рейтинги” каждой из протестированных систем (по состоянию на их последнем тестировании) таковы. Меньшее количество строк, предположительно, лучше:

Кол-во строк         Система
 22                   Pyramid (1.0a9)
 34                   Bottle (0.8.1)
 57                   Django (1.02)
 96                   Flask (0.6)
 96                   Pylons (0.9.7rc4)
 250                  Zope2 (2.10 via repoze.zope2)
 317                  Grok (1.0a1)
 398                  TurboGears 2 (2.0.3)

Значение “кол-во строк” выше, является количеством строк в выводе профайлера, порождённых 1000 хитов к разогретому приложению “hello world” для каждого фреймворка. Это число было взято из пункта «Profile lines» в каждом файле «results.txt» из каждой поддиректории данных отчета. Эта метрика подсчитывает количество выходных линий вывода профайлера Python после 1000 хитов. Подробнее смотри What's Your Web Framework Doing Under the Hood.

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

Но всё же, почему кол-во строк в Pyramid мало? Это был лучший фреймворк на этом конкретном тесте на протяжении почти года. Это не “микрофреймворк”, как может он делать меньше чем два микрофреймворка перечисленных в таблице результатов (Flask и Bottle)? Он имеет больше возможностей чем Pylons, так почему он выполняет меньше чем Pylons? Он, возможно, имеет много особенностей TurboGears, так почему он выполняет меньше TG? Если на то пошло, то почему Django выполняет меньше Pylons? Почему Flask выполняет больше, чем Django? Термины «легкий» и «микрофреймворк» не обязательно коррелирует с более высокой производительностью.

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

Интроспекция во время запуска, а не во время выполнения

Когда Pyramid запускается, он считывает конфигурацию заявленную пользователем. Большая часть кофигурационных утверждений сделанных пользоватем — о “вьюхах”.

Главная задача в жизни для Pyramid, определить какую “вьюху” (если таковые имеются) выполнить для данного запроса. “Вьюхи” это исполняемые объекты(функции или методы классов), вызываемые когда определенный URL или другой параметр запроса совпадают.

“Вьюхи” в Pyramid могут принимать различные формы. “Вьюха” может быть простой функцией. Это может быть метод класса. Это может быть объект. Несмотря на это, когда Pyramid считывает “вьюху” во время запуска, то он хорошенько рассматривает как саму “вьюху”, так и связанные с ней параметры конфигурации. Pyramid не рассматривает “вьюху” и параметры конфигурации при каждом запросе. Pyramid обрабатывает один раз во время запуска, и, если необходимо, обертывает каждую “вьюху” в одну или более функцию-обертку. Например, при запуске, если был указан рендерер, Pyramid оборачивает “вьюху” в рендерящую фунцию. Если политика авторизации указана и доступ к “вьюхе” ограничен, Pyramid может обернуть “вьюху” функцией, которая делает проверку авторизации. Если была включена отладка авторизации, она может обернуть “вьюху” в функцию, которая выдает отладочную информацию, при каждом запросе. Если “вьюха” имеет особую сигнатуру аргументов, она может обернуть “вьюху” в функцию-преобразователь, которая знает как вызывать “вьюху”. И так далее. Эти обертки исполняются как конвеер. Вот немного настоящего кода из Pyramid, который делает эту магию:


   def __call__(self, view):
       return self.attr_wrapped_view(
           self.predicated_view(
               self.authdebug_view(
                   self.secured_view(
                       self.owrapped_view(
                           self.decorated_view(
                               self.rendered_view(
                                   self.mapped_view(view))))))))

«view» в коде выше, “вьюха” определяемая пользователем. «self» является экземпляром класса pyramid.config.ViewDeriver, который содержит системные и спецефические для “вьюхи” настройки. Каждый из методов, при вызове может обернуть “вьюху”, указанную пользователем, в функцию. Если конкретная подсистема не требуется для выполнения “вьюхи”, метод просто возвращает необернутую “вьюху” (например, если не требуется проверка авторизации, так как пользователь не задал политику авторизации или не ограничил доступ к “вьюхе”, вызов self.secured_view выше, просто вернет переданную ему “вьюху”).

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

Обертка защищает “вьюху” от ненужного выполнения. Если нет никакой политики авторизации, то “вьюха” не будет обёрнута и будет вызвана напрямую. Если есть политика авторизации и доступ к “вьюхе” ограничен, авторизующая обертка выполняется первой, которая либо вызывает “вьюху” либо выдает исключение, указывая, что в доступе было отказано. В любом случае, исполняется наименьшее количество кода для каждого случая.

В общем, Pyramid делает работу при старте, чтобы не делать её при каждом запросе. Такая же оптимизация есть в коде Pyramid в других местах, не связанная с вызовом “вьюх”. Вы можете подумать что это может сделать запуск Pyramid медленным, но это не так… не смертельно, по крайней мере. Даже самое крупное Pyramid приложение запускается гораздо быстрее, чем наименьшее Grok или Zope приложение. Приложение в 100 тыс. строк под название Karl стартует менее чем за секунду. Pyramid может использоваться на Google App Engine, который имеет модель исполнения такую же, как CGI. Эта оптимизация работы довольно тривиальна. Она даже не особенно умна.

Использует C оптимизацию

Чтобы определить, должен ли конкретный вид вызываться для текущего запроса или нет, Pyramid использует оптимизацию: пакет zope.component, которая написана на C. Пакет zope.component предоставляет пирамиде «объект-реестр”, который является как бы „супер-словарем“. Очень распространенная модель доступа, используемая в Pyramid может быть сформулирована на английском, как „найди мне наиболее подходящий вид для текущего контекста и текущего запроса“. zope.component может принимать это решение очень быстро и без накладных расходов на многие вызовы функций Python. zope.component был разработан по существу, чтобы сделать этот конкретный запрос как можно быстрее.

Обманывает

Pyramid приложение „whatsitdoing“ заменяет объект “Response” из пакета WebOb более простым вариантом, который делает намного меньше. Называть это “обманом” является преувеличением, так как дизайн Pyramid позволяет это. Это даже задокументировано. Это документировано и сделано возможным, однако, поскольку мы делали системачискую оптимизацию очень рано и решили, что привязка Pyramid к конкретной реализации Response не нужна. Не все фреймворки приняли такое решение. Pyramid никогда не создает глобальный объект Response. Это еще одно конструктивное решение, которое не сильно вредит на практике и позволяет специализированным приложениям получать очень высокую производительность.

Опять же, не все фреймворки сделали такое решение.

Систематически оптимизировался человеком

Первое выполнение приложения „whatsitdoing“ на фреймворке repoze.bfg( предке Pyramid) в 2008 выдавало около 100 строк в выводе профайлера. Мы постепенно вывели на нет посторонний динамизм и несущественные функции, чтобы довести до нынешнего уровня производительности.

Выводы

В любом долго-живущем процессе, который выполняет тот же код снова и снова, необходимо сделать как можно больше работы заранее. Можно провести аналогию между тем, как фреймворки оптимизируют поиск и исполнение “вьюх” и между статической и динамичесой типизацией. Pyramid использует предоставленные пользователем инструкции конфигурирующие “вьюхи” чтобы применить оптимизацию при старте, также как компилятор использует статические объявления типов. “Компилятор” (система оборачивания “вьюх” в Pyramid) делает только столько работы, сколько необходимо и никогда не повторяет ненужные проверки при каждом запросе. Zope, Grok, TG и другие системы, по-видимому не используют потенциал заложенный в конфигурационных инструкциях, предоставляемых пользователем. Вместо этого, они, вероятно, делают больше работы, чем это строго необходимо.

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

Преждевременная оптимизация может быть корнем всех зол, но откладывание оптимизации до “после 1.0” является источником тормозов и ставит потом в затруднительное положение. Оптимизация, в то время как ваш фрейворк находится в фазе проектирования черезвычайно поучительна, и может помочь вам принять своевременные конструктивные решения, которые позволят вашей системе быть меньше и производительней.

Оригинал статьи


Последние записи блога:


Станьте первым!

Пожалуйста, авторизуйтесь или зарегистрируйтесь для комментирования!