Как я могу выполнять неэффективный код только во время компиляции при использовании mod perl?

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

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

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

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

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

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

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

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

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

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

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

обновление

я попробовал начать блок в каждом подходе модуля проекта, как описано ysth в его ответе. Так что теперь у меня есть:

package MyApp::bootstrap;
use MyApp;

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

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

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

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

6 ответов


предполагая, что ваши приложения вообще не изменяют конфигурацию, переместите ее в блок begin:

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

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

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

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


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


модуля импорт sub выполняется во время компиляции, поэтому мы могли бы использовать это для уменьшения / устранения сухого ysth-х.

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

оговоркой, либо $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'; 

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

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

в вашем handler.pl файл, вы определяете все ваши статические элементы, такие как ваша конфигурация, дескрипторы базы данных и т. д. Это определяет их в области YourModule:: Mason, которая компилируется при запуске потока apache (новые потоки, очевидно, будут иметь неотъемлемые накладные расходы). YourModule:: Mason тогда имеет handler метод, который обрабатывает запрос.

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


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

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

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

следите за gotchas, хотя:простой способ взломать


JackM имеет право идея.

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

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