Как выполнить неэффективный код только во время компиляции при использовании mod_perl? - PullRequest
9 голосов
/ 05 декабря 2008

Я тестировал производительность фреймворка, который пишу на Perl, и получаю на 50% меньше запросов в секунду по сравнению с нашей существующей кодовой базой (некоторые попадания понятны, потому что мы идем от процедурных спагетти код для структуры ООП MVC).

Приложение работает под mod_perl, и я добавил Moose и весь мой код фреймворка в скрипт startup.pl , который сам удваивал количество моих запросов в секунду. Я рассчитываю еще больше увеличить это число, чтобы максимально приблизить его к существующей сумме. Есть аргумент, что это преждевременная оптимизация, но есть пара явных недостатков, которые я бы хотел исправить и посмотреть, как это влияет на производительность.

Как и большинство фреймворков, у меня есть файл конфигурации и диспетчер. Часть конфигурации обрабатывается Config :: General , поэтому для загрузки моего файла конфигурации в приложение требуется немного ввода-вывода и анализа. Самая большая проблема, которую я вижу здесь, состоит в том, что я делаю это для КАЖДОГО ЗАПРОСА, который приходит!

Запуск Devel :: Dprof в моем приложении указывает на Config :: General :: BEGIN и несколько связанных модулей ввода-вывода в качестве одной из основных медленных точек, которой не является Moose. Итак, что я хотел бы сделать, и что гораздо важнее в ретроспективе, так это воспользоваться постоянством mod_perl и возможностью компиляции startup.pl, чтобы выполнять работу по загрузке в файл конфигурации только один раз - при запуске сервера.

Проблема в том, что я не слишком знаком с тем, как это будет работать.

В настоящее время каждый проект имеет класс начальной загрузки PerlHandler, который довольно прост и выглядит следующим образом:

use MyApp; 
MyApp->new(config_file => '/path/to/site.config')->run();

MyApp.pm наследуется от модуля Project, который имеет следующий код:

my $config = Config::General->new(
                -ConfigFile => $self->config_file,
                -InterPolateVars => 1,
             );    

$self->config({$config->getall});

Чтобы сделать это только во время компиляции, нужно будет изменить и мои модули начальной загрузки, и базовые модули Project (я думаю), но я не совсем уверен в том, какие изменения необходимо внести, и при этом сохранить код приятным и компактным. Кто-нибудь может указать мне правильное направление здесь?

UPDATE

Я попробовал НАЧАТЬ БЛОК в каждом подходе модуля проекта, как описано в его ответе. Итак, теперь у меня есть:

package MyApp::bootstrap;
use MyApp;

my $config;
BEGIN
{
    $config = {Config::General->new(...)->getall};        
}

sub handler { ..etc.
    MyApp->new(config => $config)->run();

Одно только это быстрое изменение дало мне увеличение запросов в секунду на 50% , подтверждая мои мысли о том, что файл конфигурации был серьезным узким местом, которое стоит исправить. Контрольная цифра на нашей капризной старой машине разработки составляет 60 оборотов в секунду, и мой каркас изменился с 30 об / с до 45 оборотов в минуту только с этим изменением. Для тех, кто говорит, что Moose работает медленно и имеет время компиляции ... При компиляции всего моего кода Moose при запуске я получил такое же (50%) увеличение, как и при предварительной компиляции файла конфигурации.

Единственная проблема, с которой я столкнулся сейчас, заключается в том, что это нарушает принцип DRY, так как один и тот же код Config :: General-> находится в каждом блоке BEGIN, и путь к файлу конфигурации различен. У меня есть несколько разных стратегий, чтобы ограничить это, но я просто хотел опубликовать результаты этого изменения.

Ответы [ 6 ]

10 голосов
/ 05 декабря 2008

Если ваши приложения вообще не меняют конфигурацию, переместите ее в начальный блок:

# this code goes at file scope
my $config;
BEGIN {
    $config = { Config::General->new( ... )->getall }
}

# when creating a new instance
$self->config( $config );

И убедитесь, что все ваши модули скомпилированы в файле startup.pl.

Вы можете стать более любопытным, и у одноэлементного класса будет хеш конфигурации, но вам не нужно.

4 голосов
/ 05 декабря 2008

Если вы можете сделать свои классы лося неизменными , это может дать вам еще один скачок скорости.

3 голосов
/ 16 июля 2009

Модуль import sub модуля выполняется во время компиляции, поэтому мы могли бы использовать это для уменьшения / устранения СУХОЙ ответа ysth .

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

Предостережение, являющееся любой переменной $config в вызывающем пакете, будет уничтожено этим.

package Foo_Config;
use English qw(-no_match_vars);
sub import {
   my ($self, @cfg) = @ARG;
   my $call_pkg     = caller;
   my $config       = {Config::General->new(@cfg)->getall};
   do{ # this will create the $config variable in the calling package.
       no strict 'refs';
       ${$call_pkg . '::config'} = $config;
   };
   return;
}

package MyApp;
# will execute Foo_Config->import('/path/to/site.config') at compile time.
use Foo_Config '/path/to/site.config'; 
1 голос
/ 05 декабря 2008

У меня были те же проблемы при установке фреймворка HTML :: Mason, и я обнаружил, что это работает довольно хорошо: В httpd.conf:

PerlRequire handler.pl
<FilesMatch "\.mhtml$">
  SetHandler perl-script
  PerlHandler YourModule::Mason
</FilesMatch>

В вашем файле handler.pl вы определяете все ваши статические элементы, такие как ваш config, дескрипторы базы данных и т. Д. Это определяет их в области действия YourModule :: Mason, которая компилируется при запуске потока apache (новые потоки, очевидно, будут имеют врожденные накладные расходы). Затем YourModule :: Mason имеет метод handler, который обрабатывает запрос.

Я признаю, что в HTML может происходить какая-то магия :: Мейсон, который помогает мне в этом, но это работает для меня, а может для вас?

0 голосов
/ 22 декабря 2008

Обычный способ ускорить такие вещи с небольшими изменениями - просто использовать глобальные переменные и кешировать в них состояние между вызовами одного и того же процесса Apache:

use vars qw ($config);
# ...
$config = Config::General->new( ... )->getall
    unless blessed($config); # add more suitable test here

Он не очень чистый и может привести к неясным ошибкам (хотя «мой $ var» в моем опыте приводит к большему »), и иногда он потребляет много памяти, но таким образом можно избежать многих (повторяющихся) дорогостоящих операторов инициализации. Преимущество перед использованием BEGIN {}; только код заключается в том, что вы можете повторно инициализировать на основе других событий, а также без необходимости перезапускать Apache или убивать ваш процесс (например, путем включения метки времени файла на диске в тесте выше).

Остерегайтесь ошибок, хотя: простой способ взломать

0 голосов
/ 05 декабря 2008

JackM имеет правильную идею.

Загружая все ваши классы и создавая экземпляры объектов уровня приложения (в вашем случае, конфигурации) в процессе Apache " Mother ", вам не нужно каждый раз компилировать их рабочий появляется, так как они уже доступны и находятся в памяти. Очень дотошные среди нас добавляют строку «использования» для каждого модуля, который их приложение регулярно использует. Если вы не загружаете свои пакеты и модули на материнскую плату, каждый работник не только снижает производительность загрузки модулей, но и не получает преимущества совместного использования памяти, предоставляемого современными операционными системами.

Это действительно другая половина разницы между mod_perl и CGI. С первой половиной является постоянный perl-движок mod_perl против возрождающегося perl CGI для каждого вызова.

...