perlperf
СОДЕРЖАНИЕ
- ИМЯ
- ОПИСАНИЕ
- ОБЗОР
- ОБЩИЕ РЕКОМЕНДАЦИИ
- БЕНЧМАРКИ
- ИНСТРУМЕНТЫ ПРОФИЛИРОВАНИЯ
- СОРТИРОВКА
- ВЕДЕНИЕ ЖУРНАЛА
- POSTSCRIPT
- СМОТРИТЕ ТАКЖЕ
- АВТОР
ИМЯ
perlperf - Perl-техники производительности и оптимизации
ОПИСАНИЕ
Это введение в использование техник производительности и оптимизации, которые могут быть применены к программам Perl. Хотя многие разработчики Perl пришли из других языков и могут использовать свои предыдущие знания по мере необходимости, многие другие люди могут извлечь пользу из нескольких специфических для Perl советов. Если вам нужна сжатая версия, пожалуй, лучший совет исходит от известного японского самурая Миямото Мусаси, который сказал:
"Do Not Engage in Useless Activity" в 1645 году.
ОБЗОР
Возможно, самая распространённая ошибка программистов заключается в том, что они пытаются оптимизировать свой код до того, как программа сделает что-то полезное — это плохая идея. Нет смысла иметь чрезвычайно быструю программу, которая не работает. Первая задача — заставить программу *правильно* делать что-то *полезное* (не говоря уже о том, чтобы убедиться, что пакет тестов полностью функционален), и только тогда стоит подумать об оптимизации. Решив оптимизировать существующий рабочий код, следует рассмотреть несколько простых, но важных шагов, которые являются неотъемлемой частью любого процесса оптимизации.
ОДИН ШАГ ВСТОРОНУ
Во-первых, необходимо установить базовый временной интервал для существующего кода, который должен быть надёжным и воспроизводимым. Вероятно, вы захотите использовать модули Benchmark или Devel::NYTProf, или что-то подобное, для этого этапа, или, возможно, утилиту Unix time, что более подходит. Смотрите в начале этого документа более подробный список модулей для бенчмаркинга и профилирования, а также рекомендуемые дальнейшие материалы.
ОДИН ШАГ ВПЕРЕД
Далее, изучив программу на предмет *узких мест* (места, где код, похоже, работает медленно), измените код с намерением ускорить его выполнение. Использование системы контроля версий, такой как subversion, обеспечит, что никакие изменения не будут необратимыми. Очень легко что-то поправить здесь и там — не меняйте слишком много за один раз, иначе вы можете не обнаружить, какой именно фрагмент кода *действительно* был медленным.
ЕЩЕ ОДИН ШАГ ВСТОРОНУ
Недостаточно сказать: «это ускорит его работу». Нужно проверить это. Запустите код под управлением модулей для бенчмаркинга или профилирования, которые были указаны на предыдущем шаге, и убедитесь, что новый код выполнил *ту же задачу* за *меньшее время*. Сохраните свою работу и повторите...
ОБЩИЕ РЕКОМЕНДАЦИИ
При рассмотрении производительности крайне важно помнить, что не существует такого понятия, как *золотая пуля*, поэтому нет правил, только рекомендации.
Очевидно, что встроенный код будет быстрее, чем вызовы подпрограмм или методов, потому что накладные расходы меньше, но этот подход имеет недостаток меньшей поддерживаемости и влечёт за собой увеличение использования памяти — нет ничего даром. Если вы ищете элемент в списке, может быть эффективнее хранить данные в структуре хеша, а затем просто проверить, определён ли ключ, вместо того, чтобы перебирать весь массив, используя grep() или аналогичные методы. substr() может быть (намного) быстрее, чем grep(), но не таким гибким, поэтому вы имеете ещё один компромисс в доступе. Ваш код может содержать строку, которая выполняется за 0,01 секунды, но если вы вызываете её 1000 раз, скорее всего, при обработке даже средних по размеру файлов, у вас уже будет задержка в 10 секунд только в одной строке кода. Если вы вызовете эту строку 100 000 раз, вся программа замедлится до неприемлемого уровня.
Использование подпрограммы в качестве части вашей сортировки — мощный способ получить именно то, что нужно, но обычно медленнее, чем встроенные операторы сортировки по *алфавиту* cmp и *числу* <=>. Возможно создание нескольких проходов по данным, построение индексов для повышения эффективности последующей сортировки и использование так называемого OM (оркинг манёвр) для кеширования ключей сортировки заранее. Поиск в кеше, хотя и хорошая идея, может сам по себе стать источником замедления, выполняя двойной проход по данным — один для настройки кеша и один для сортировки данных. Использование pack() для извлечения необходимого ключа сортировки в согласованную строку может быть эффективным способом построения одной строки для сравнения вместо использования нескольких ключей сортировки, что позволяет использовать стандартную, написанную на c и быструю, функцию Perl sort() для вывода и является основой GRT (преобразование Гутмана-Росслера). Некоторые комбинации строк могут замедлить GRT просто по причине избыточной сложности.
Для приложений, использующих базы данных, стандартный DBIx имеет средства для ускорения работы, не в последнюю очередь благодаря тому, что он пытается *не* запрашивать базу данных до последнего возможного момента, но всегда читайте документацию, прилагаемую к вашему выбору библиотек. Среди множества проблем, с которыми сталкиваются разработчики, работающие с базами данных, следует помнить о том, чтобы всегда использовать SQL и рассматривать предварительную выборку наборов данных, когда это может оказаться целесообразным. Разделение большого файла путём назначения нескольких процессов для обработки одного файла, используя, например, POE, threads или fork, также может быть полезным способом оптимизации использования доступных CPU ресурсов, хотя эта техника полна проблем с конкурентностью и требует пристального внимания к деталям.
В каждом случае есть специфическое применение и один или несколько исключений, и нет замены для проведения нескольких тестов и выяснения того, какой метод лучше всего подходит для вашей конкретной среды. Именно поэтому написание оптимального кода — не точная наука и почему нам так нравится использовать Perl — TMTOWTDI.
БЕНЧМАРКИ
Вот несколько примеров, демонстрирующих использование средств бенчмаркинга Perl.
Присваивание и разыменование переменных.
Я уверен, что большинство из нас видели код, который выглядит так (или хуже):
if ( $obj->{_ref}->{_myscore} >= $obj->{_ref}->{_yourscore} ) {
... Такой код может быть действительно неприятным для чтения, а также очень чувствительным к опечаткам, и гораздо яснее разыменовывать переменную явно. Мы обходим проблему работы с методами объектно-ориентированного программирования для инкапсуляции доступа к переменным через методы, доступные только через объект. Здесь мы просто обсуждаем техническое решение, подходящее по умолчанию, и влияет ли это на производительность. Мы можем проверить, есть ли накладные расходы у этой операции разыменования, разместив сравнительный код в файле и выполнив тест Benchmark.
# разыменование
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark;
my $ref = {
'ref' => {
_myscore => '100 + 1',
_yourscore => '102 - 1',
},
};
timethese(1000000, {
'direct' => sub {
my $x = $ref->{ref}->{_myscore} . $ref->{ref}->{_yourscore} ;
},
'dereference' => sub {
my $ref = $ref->{ref};
my $myscore = $ref->{_myscore};
my $yourscore = $ref->{_yourscore};
my $x = $myscore . $yourscore;
},
}); Необходимо запускать любые измерения времени достаточное количество раз, чтобы получить среднее числовое значение, иначе каждое выполнение будет естественным образом варьироваться из-за колебаний среды, чтобы уменьшить влияние конкуренции за CPU ресурсы и пропускную способность сети, например. Запустив указанный выше код для одного миллиона итераций, мы можем взглянуть на вывод отчёта модуля Benchmark, чтобы увидеть, какой подход является наиболее эффективным.
$> perl dereference
Benchmark: timing 1000000 iterations of dereference, direct...
dereference: 2 wallclock secs ( 1.59 usr + 0.00 sys = 1.59 CPU) @ 628930.82/s (n=1000000)
direct: 1 wallclock secs ( 1.20 usr + 0.00 sys = 1.20 CPU) @ 833333.33/s (n=1000000) Разница хорошо видна, и подход с разыменованием медленнее. Хотя ему удалось выполнить в среднем 628 930 раз в секунду во время нашего теста, прямому подходу удалось выполнить ещё 204 403 раза, к сожалению. К сожалению, так как существует множество примеров кода, написанного с использованием многоуровневого прямого доступа к переменным, и он обычно ужасен. Тем не менее, он незначительно быстрее. Вопрос в том, стоит ли незначительный выигрыш напряжения глаз или потери поддерживаемости.
Поиск и замена или tr
Если нам нужно изменить строку, хотя регулярное выражение почти всегда будет намного гибче, tr, часто недооцениваемый инструмент, по-прежнему может быть полезным. Одна из ситуаций — замена всех гласных на другой символ. Решение с регулярным выражением может выглядеть так:
$str =~ s/[aeiou]/x/g Альтернативный вариант с tr может выглядеть так:
$str =~ tr/aeiou/xxxxx/ Мы можем поместить это в тестовый файл, который мы можем запустить, чтобы проверить, какой подход является самым быстрым, используя глобальную $STR переменную для присвоения переменной my $str, чтобы избежать попыток Perl оптимизировать работу, заметив, что она назначена только один раз.
# regex-transliterate
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark;
my $STR = "$$-this and that";
timethese( 1000000, {
'sr' => sub { my $str = $STR; $str =~ s/[aeiou]/x/g; return $str; },
'tr' => sub { my $str = $STR; $str =~ tr/aeiou/xxxxx/; return $str; },
}); Запустив код, мы получим результаты:
$> perl regex-transliterate
Benchmark: timing 1000000 iterations of sr, tr...
sr: 2 wallclock secs ( 1.19 usr + 0.00 sys = 1.19 CPU) @ 840336.13/s (n=1000000)
tr: 0 wallclock secs ( 0.49 usr + 0.00 sys = 0.49 CPU) @ 2040816.33/s (n=1000000) Версия с tr — явный победитель. Одно решение гибкое, другое — быстрое, и разработчик вправе выбрать, какое использовать.
Обратитесь к документации Benchmark для получения дополнительных полезных техник.
ИНСТРУМЕНТЫ ПРОФИЛИРОВАНИЯ
Несколько более крупный фрагмент кода предоставит что-то, на чём профилировщик может составить более подробный отчёт о статистических данных. Этот пример использует простую программу wordmatch, которая анализирует указанный входной файл и выводит короткий отчёт о содержимом.
# wordmatch
#!/usr/bin/perl
use strict;
use warnings;
=head1 NAME
filewords - word analysis of input file
=head1 SYNOPSIS
filewords -f inputfilename [-d]
=head1 DESCRIPTION
This program parses the given filename, specified with C<-f>, and
displays a simple analysis of the words found therein. Use the C<-d>
switch to enable debugging messages.
=cut
use FileHandle;
use Getopt::Long;
my $debug = 0;
my $file = '';
my $result = GetOptions (
'debug' => \$debug,
'file=s' => \$file,
);
die("invalid args") unless $result;
unless ( -f $file ) {
die("Usage: $0 -f filename [-d]");
}
my $FH = FileHandle->new("< $file")
or die("unable to open file($file): $!");
my $i_LINES = 0;
my $i_WORDS = 0;
my %count = ();
my @lines = <$FH>;
foreach my $line ( @lines ) {
$i_LINES++;
$line =~ s/\n//;
my @words = split(/ +/, $line);
my $i_words = scalar(@words);
$i_WORDS = $i_WORDS + $i_words;
debug("line: $i_LINES supplying $i_words words: @words");
my $i_word = 0;
foreach my $word ( @words ) {
$i_word++;
$count{$i_LINES}{spec} += matches($i_word, $word,
'[^a-zA-Z0-9]');
$count{$i_LINES}{only} += matches($i_word, $word,
'^[^a-zA-Z0-9]+$');
$count{$i_LINES}{cons} += matches($i_word, $word,
'^[(?i:bcdfghjklmnpqrstvwxyz)]+$');
$count{$i_LINES}{vows} += matches($i_word, $word,
'^[(?i:aeiou)]+$');
$count{$i_LINES}{caps} += matches($i_word, $word,
'^[(A-Z)]+$');
}
}
print report( %count );
sub matches {
my $i_wd = shift;
my $word = shift;
my $regex = shift;
my $has = 0;
if ( $word =~ /($regex)/ ) {
$has++ if $1;
}
debug( "word: $i_wd "
. ($has ? 'matches' : 'does not match')
. " chars: /$regex/");
return $has;
}
sub report {
my %report = @_;
my %rep;
foreach my $line ( keys %report ) {
foreach my $key ( keys %{ $report{$line} } ) {
$rep{$key} += $report{$line}{$key};
}
}
my $report = qq|
$0 report for $file:
lines in file: $i_LINES
words in file: $i_WORDS
words with special (non-word) characters: $i_spec
words with only special (non-word) characters: $i_only
words with only consonants: $i_cons
words with only capital letters: $i_caps
words with only vowels: $i_vows
|;
return $report;
}
sub debug {
my $message = shift;
if ( $debug ) {
print STDERR "DBG: $message\n";
}
}
exit 0; Devel::DProf
Этот уважаемый модуль был фактическим стандартом профилирования кода Perl более десяти лет, но был заменён рядом других модулей, которые вернули нас в 21 век. Хотя рекомендуется оценить ваш инструмент из нескольких упомянутых здесь и из списка CPAN внизу этого документа, (и в настоящее время Devel::NYTProf кажется предпочтительным оружием - см. ниже), мы сначала кратко рассмотрим вывод из Devel::DProf, чтобы установить базовый уровень для инструментов профилирования Perl. Запустите вышеприведённую программу под управлением Devel::DProf с помощью переключателя -d в командной строке.
$> perl -d:DProf wordmatch -f perl5db.pl
<...multiple lines snipped...>
wordmatch report for perl5db.pl:
lines in file: 9428
words in file: 50243
words with special (non-word) characters: 20480
words with only special (non-word) characters: 7790
words with only consonants: 4801
words with only capital letters: 1316
words with only vowels: 1701 Devel::DProf генерирует специальный файл, по умолчанию названный tmon.out, и этот файл считывается программой dprofpp, которая уже установлена в составе дистрибутива Devel::DProf. Если вы вызовете dprofpp без опций, она прочитает файл tmon.out в текущем каталоге и создаст удобочитаемый отчёт о статистике выполнения вашей программы. Обратите внимание, что это может занять некоторое время.
$> dprofpp
Total Elapsed Time = 2.951677 Seconds
User+System Time = 2.871677 Seconds
Exclusive Times
%Time ExclSec CumulS #Calls sec/call Csec/c Name
102. 2.945 3.003 251215 0.0000 0.0000 main::matches
2.40 0.069 0.069 260643 0.0000 0.0000 main::debug
1.74 0.050 0.050 1 0.0500 0.0500 main::report
1.04 0.030 0.049 4 0.0075 0.0123 main::BEGIN
0.35 0.010 0.010 3 0.0033 0.0033 Exporter::as_heavy
0.35 0.010 0.010 7 0.0014 0.0014 IO::File::BEGIN
0.00 - -0.000 1 - - Getopt::Long::FindOption
0.00 - -0.000 1 - - Symbol::BEGIN
0.00 - -0.000 1 - - Fcntl::BEGIN
0.00 - -0.000 1 - - Fcntl::bootstrap
0.00 - -0.000 1 - - warnings::BEGIN
0.00 - -0.000 1 - - IO::bootstrap
0.00 - -0.000 1 - - Getopt::Long::ConfigDefaults
0.00 - -0.000 1 - - Getopt::Long::Configure
0.00 - -0.000 1 - - Symbol::gensym dprofpp создаст довольно подробный отчёт о деятельности программы wordmatch. Вверху анализа указаны время, затраченное на выполнение, время пользователя и время системы, а после них указаны основные столбцы, определяющие отчёт. См. документацию dprofpp для получения подробностей о поддерживаемых опциях.
См. также Apache::DProf, который подключает Devel::DProf к mod_perl.
Devel::Profiler
Давайте рассмотрим ту же программу с помощью другого профилировщика: Devel::Profiler, прямой заменой для Devel::DProf только для Perl. Использование немного отличается, так как вместо использования специального флага -d:, вы подключаете Devel::Profiler напрямую как модуль с помощью -M.
$> perl -MDevel::Profiler wordmatch -f perl5db.pl
<...multiple lines snipped...>
wordmatch report for perl5db.pl:
lines in file: 9428
words in file: 50243
words with special (non-word) characters: 20480
words with only special (non-word) characters: 7790
words with only consonants: 4801
words with only capital letters: 1316
words with only vowels: 1701 Devel::Profiler генерирует файл tmon.out, совместимый с программой dprofpp, тем самым экономя создание отдельной программы-читателя статистики. dprofpp использование, следовательно, идентично вышеприведённому примеру.
$> dprofpp
Total Elapsed Time = 20.984 Seconds
User+System Time = 19.981 Seconds
Exclusive Times
%Time ExclSec CumulS #Calls sec/call Csec/c Name
49.0 9.792 14.509 251215 0.0000 0.0001 main::matches
24.4 4.887 4.887 260643 0.0000 0.0000 main::debug
0.25 0.049 0.049 1 0.0490 0.0490 main::report
0.00 0.000 0.000 1 0.0000 0.0000 Getopt::Long::GetOptions
0.00 0.000 0.000 2 0.0000 0.0000 Getopt::Long::ParseOptionSpec
0.00 0.000 0.000 1 0.0000 0.0000 Getopt::Long::FindOption
0.00 0.000 0.000 1 0.0000 0.0000 IO::File::new
0.00 0.000 0.000 1 0.0000 0.0000 IO::Handle::new
0.00 0.000 0.000 1 0.0000 0.0000 Symbol::gensym
0.00 0.000 0.000 1 0.0000 0.0000 IO::File::open Интересно, что мы получаем немного разные результаты, что в основном из-за того, что алгоритм, генерирующий отчёт, отличается, даже если формат выходного файла, предположительно, идентичен. Время выполнения, время пользователя и время системы чётко показывают время, необходимое для выполнения Devel::Profiler своей работы, но перечень столбцов, как будто, более точны, чем те, что были раньше от Devel::DProf. Например, цифра 102% исчезла. Здесь нам нужно использовать имеющиеся инструменты и понимать их плюсы и минусы, прежде чем использовать их. Интересно, что количество вызовов для каждой подпрограммы идентично в двух отчётах, отличаются только проценты. Как пишет автор Devel::Proviler:
...running HTML::Template's test suite under Devel::DProf shows
output() taking NO time but Devel::Profiler shows around 10% of the
time is in output(). I don't know which to trust but my gut tells me
something is wrong with Devel::DProf. HTML::Template::output() is a
big routine that's called for every test. Either way, something needs
fixing. Результат может варьироваться.
См. также Devel::Apache::Profiler, который подключает Devel::Profiler к mod_perl.
Devel::SmallProf
Профилировщик Devel::SmallProf исследует время выполнения вашей программы Perl и генерирует построчное представление, чтобы показать, сколько раз каждая строка вызывалась и сколько времени потребовалось для выполнения каждой строки. Он вызывается путём указания знакомого флага -d для Perl во время выполнения.
$> perl -d:SmallProf wordmatch -f perl5db.pl
<...multiple lines snipped...>
wordmatch report for perl5db.pl:
lines in file: 9428
words in file: 50243
words with special (non-word) characters: 20480
words with only special (non-word) characters: 7790
words with only consonants: 4801
words with only capital letters: 1316
words with only vowels: 1701 Devel::SmallProf записывает свой вывод в файл smallprof.out по умолчанию. Формат файла выглядит следующим образом:
<num> <time> <ctime> <line>:<text> По завершении программы вывод можно просмотреть и отсортировать с помощью стандартных инструментов фильтрации текста. Что-то вроде следующего может быть достаточно:
$> cat smallprof.out | grep \d*: | sort -k3 | tac | head -n20
251215 1.65674 7.68000 75: if ( $word =~ /($regex)/ ) {
251215 0.03264 4.40000 79: debug("word: $i_wd ".($has ? 'matches' :
251215 0.02693 4.10000 81: return $has;
260643 0.02841 4.07000 128: if ( $debug ) {
260643 0.02601 4.04000 126: my $message = shift;
251215 0.02641 3.91000 73: my $has = 0;
251215 0.03311 3.71000 70: my $i_wd = shift;
251215 0.02699 3.69000 72: my $regex = shift;
251215 0.02766 3.68000 71: my $word = shift;
50243 0.59726 1.00000 59: $count{$i_LINES}{cons} =
50243 0.48175 0.92000 61: $count{$i_LINES}{spec} =
50243 0.00644 0.89000 56: my $i_cons = matches($i_word, $word,
50243 0.48837 0.88000 63: $count{$i_LINES}{caps} =
50243 0.00516 0.88000 58: my $i_caps = matches($i_word, $word, '^[(A-
50243 0.00631 0.81000 54: my $i_spec = matches($i_word, $word, '[^a-
50243 0.00496 0.80000 57: my $i_vows = matches($i_word, $word,
50243 0.00688 0.80000 53: $i_word++;
50243 0.48469 0.79000 62: $count{$i_LINES}{only} =
50243 0.48928 0.77000 60: $count{$i_LINES}{vows} =
50243 0.00683 0.75000 55: my $i_only = matches($i_word, $word, '^[^a- Вы сразу можете увидеть несколько иную направленность на профилирование подпрограмм, и мы начинаем видеть именно какую строку кода занимает больше всего времени. Например, строка с регулярным выражением выглядит подозрительно. Помните, что эти инструменты должны использоваться вместе, нет единственного наилучшего способа профилирования кода, вам нужно использовать лучшие инструменты для конкретной задачи.
См. также Apache::SmallProf , который подключает Devel::SmallProf к mod_perl.
Devel::FastProf
Devel::FastProf — ещё один профилировщик строк Perl. Он был написан с целью получения более быстрого профилировщика строк, чем, например, Devel::SmallProf, потому что он написан на C Для использования Devel::FastProf, укажите аргумент -d для Perl:
$> perl -d:FastProf wordmatch -f perl5db.pl
<...multiple lines snipped...>
wordmatch report for perl5db.pl:
lines in file: 9428
words in file: 50243
words with special (non-word) characters: 20480
words with only special (non-word) characters: 7790
words with only consonants: 4801
words with only capital letters: 1316
words with only vowels: 1701 Devel::FastProf записывает статистику в файл fastprof.out в текущем каталоге. Файл вывода, который можно указать, может быть интерпретирован с помощью программы командной строки fprofpp.
$> fprofpp | head -n20
# fprofpp output format is:
# filename:line time count: source
wordmatch:75 3.93338 251215: if ( $word =~ /($regex)/ ) {
wordmatch:79 1.77774 251215: debug("word: $i_wd ".($has ? 'matches' : 'does not match')." chars: /$regex/");
wordmatch:81 1.47604 251215: return $has;
wordmatch:126 1.43441 260643: my $message = shift;
wordmatch:128 1.42156 260643: if ( $debug ) {
wordmatch:70 1.36824 251215: my $i_wd = shift;
wordmatch:71 1.36739 251215: my $word = shift;
wordmatch:72 1.35939 251215: my $regex = shift; Сразу же мы можем увидеть, что количество вызовов каждой строки идентично выводу Devel::SmallProf, а последовательность отличается лишь незначительно в зависимости от порядка затраченного времени на выполнение каждой строки, if ( $debug ) { и my $message = shift;, например. Различия в фактических записываемых временах могут быть связаны с используемым внутренним алгоритмом, либо с ограничениями или конфликтами системных ресурсов.
См. также DBIx::Profile, который будет профилировать запросы к базе данных, выполняемые в пространстве имён DBIx::*.
Devel::NYTProf
Devel::NYTProf — это следующее поколение профилировщика кода Perl, исправляющее многие недостатки других инструментов и реализующее множество интересных функций. Во-первых, он может использоваться как профилировщик строк, блоков или подпрограмм одновременно. Он также может использовать разрешение в доли микросекунды (100 нс) на системах, которые предоставляют clock_gettime() Он может запускаться и останавливаться даже программой, которая профилируется. Это однострочная запись для профилирования mod_perl приложений. Он написан на c и, вероятно, является самым быстрым доступным профилировщиком для Perl. Список преимуществ просто продолжается. Достаточно об этом, давайте посмотрим, как это работает — просто используйте знакомый переключатель -d для подключения и запуска кода.
$> perl -d:NYTProf wordmatch -f perl5db.pl
wordmatch report for perl5db.pl:
lines in file: 9427
words in file: 50243
words with special (non-word) characters: 20480
words with only special (non-word) characters: 7790
words with only consonants: 4801
words with only capital letters: 1316
words with only vowels: 1701 NYTProf сгенерирует базу отчётов в файл nytprof.out по умолчанию. Удобные удобочитаемые отчёты можно сгенерировать отсюда, используя предоставленные программы nytprofhtml (HTML вывод) и nytprofcsv (CSV вывод). Здесь мы использовали утилиту Unix html2text для преобразования файла nytprof/index.html для удобства.
$> html2text nytprof/index.html
Performance Profile Index
For wordmatch
Run on Fri Sep 26 13:46:39 2008
Reported on Fri Sep 26 13:47:23 2008
Top 15 Subroutines -- ordered by exclusive time
|Calls |P |F |Inclusive|Exclusive|Subroutine |
| | | |Time |Time | |
|251215|5 |1 |13.09263 |10.47692 |main:: |matches |
|260642|2 |1 |2.71199 |2.71199 |main:: |debug |
|1 |1 |1 |0.21404 |0.21404 |main:: |report |
|2 |2 |2 |0.00511 |0.00511 |XSLoader:: |load (xsub) |
|14 |14|7 |0.00304 |0.00298 |Exporter:: |import |
|3 |1 |1 |0.00265 |0.00254 |Exporter:: |as_heavy |
|10 |10|4 |0.00140 |0.00140 |vars:: |import |
|13 |13|1 |0.00129 |0.00109 |constant:: |import |
|1 |1 |1 |0.00360 |0.00096 |FileHandle:: |import |
|3 |3 |3 |0.00086 |0.00074 |warnings::register::|import |
|9 |3 |1 |0.00036 |0.00036 |strict:: |bits |
|13 |13|13|0.00032 |0.00029 |strict:: |import |
|2 |2 |2 |0.00020 |0.00020 |warnings:: |import |
|2 |1 |1 |0.00020 |0.00020 |Getopt::Long:: |ParseOptionSpec|
|7 |7 |6 |0.00043 |0.00020 |strict:: |unimport |
For more information see the full list of 189 subroutines. Первая часть отчёта уже показывает важную информацию о том, какие подпрограммы используют больше всего времени. Следующая предоставляет статистику о профилированных исходных файлах.
Source Code Files -- ordered by exclusive time then name
|Stmts |Exclusive|Avg. |Reports |Source File |
| |Time | | | |
|2699761|15.66654 |6e-06 |line . block . sub|wordmatch |
|35 |0.02187 |0.00062|line . block . sub|IO/Handle.pm |
|274 |0.01525 |0.00006|line . block . sub|Getopt/Long.pm |
|20 |0.00585 |0.00029|line . block . sub|Fcntl.pm |
|128 |0.00340 |0.00003|line . block . sub|Exporter/Heavy.pm |
|42 |0.00332 |0.00008|line . block . sub|IO/File.pm |
|261 |0.00308 |0.00001|line . block . sub|Exporter.pm |
|323 |0.00248 |8e-06 |line . block . sub|constant.pm |
|12 |0.00246 |0.00021|line . block . sub|File/Spec/Unix.pm |
|191 |0.00240 |0.00001|line . block . sub|vars.pm |
|77 |0.00201 |0.00003|line . block . sub|FileHandle.pm |
|12 |0.00198 |0.00016|line . block . sub|Carp.pm |
|14 |0.00175 |0.00013|line . block . sub|Symbol.pm |
|15 |0.00130 |0.00009|line . block . sub|IO.pm |
|22 |0.00120 |0.00005|line . block . sub|IO/Seekable.pm |
|198 |0.00085 |4e-06 |line . block . sub|warnings/register.pm|
|114 |0.00080 |7e-06 |line . block . sub|strict.pm |
|47 |0.00068 |0.00001|line . block . sub|warnings.pm |
|27 |0.00054 |0.00002|line . block . sub|overload.pm |
|9 |0.00047 |0.00005|line . block . sub|SelectSaver.pm |
|13 |0.00045 |0.00003|line . block . sub|File/Spec.pm |
|2701595|15.73869 | |Total |
|128647 |0.74946 | |Average |
| |0.00201 |0.00003|Median |
| |0.00121 |0.00003|Deviation |
Report produced by the NYTProf 2.03 Perl profiler, developed by Tim Bunce and
Adam Kaplan. На этом этапе, если вы используете отчёт в формате html, вы можете переходить по различным ссылкам, чтобы углубиться в каждую подпрограмму и каждую строку кода. Поскольку мы используем здесь текстовый отчёт и существует целая директория отчётов, сгенерированных для каждого исходного файла, мы просто отобразим часть соответствующего файла wordmatch-line.html, чтобы дать представление о типе вывода, который вы можете ожидать от этого отличного инструмента.
$> html2text nytprof/wordmatch-line.html
Performance Profile -- -block view-.-line view-.-sub view-
For wordmatch
Run on Fri Sep 26 13:46:39 2008
Reported on Fri Sep 26 13:47:22 2008
File wordmatch
Subroutines -- ordered by exclusive time
|Calls |P|F|Inclusive|Exclusive|Subroutine |
| | | |Time |Time | |
|251215|5|1|13.09263 |10.47692 |main::|matches|
|260642|2|1|2.71199 |2.71199 |main::|debug |
|1 |1|1|0.21404 |0.21404 |main::|report |
|0 |0|0|0 |0 |main::|BEGIN |
|Line|Stmts.|Exclusive|Avg. |Code |
| | |Time | | |
|1 | | | |#!/usr/bin/perl |
|2 | | | | |
| | | | |use strict; |
|3 |3 |0.00086 |0.00029|# spent 0.00003s making 1 calls to strict:: |
| | | | |import |
| | | | |use warnings; |
|4 |3 |0.01563 |0.00521|# spent 0.00012s making 1 calls to warnings:: |
| | | | |import |
|5 | | | | |
|6 | | | |=head1 NAME |
|7 | | | | |
|8 | | | |filewords - word analysis of input file |
<...snip...>
|62 |1 |0.00445 |0.00445|print report( %count ); |
| | | | |# spent 0.21404s making 1 calls to main::report|
|63 | | | | |
| | | | |# spent 23.56955s (10.47692+2.61571) within |
| | | | |main::matches which was called 251215 times, |
| | | | |avg 0.00005s/call: # 50243 times |
| | | | |(2.12134+0.51939s) at line 57 of wordmatch, avg|
| | | | |0.00005s/call # 50243 times (2.17735+0.54550s) |
|64 | | | |at line 56 of wordmatch, avg 0.00005s/call # |
| | | | |50243 times (2.10992+0.51797s) at line 58 of |
| | | | |wordmatch, avg 0.00005s/call # 50243 times |
| | | | |(2.12696+0.51598s) at line 55 of wordmatch, avg|
| | | | |0.00005s/call # 50243 times (1.94134+0.51687s) |
| | | | |at line 54 of wordmatch, avg 0.00005s/call |
| | | | |sub matches { |
<...snip...>
|102 | | | | |
| | | | |# spent 2.71199s within main::debug which was |
| | | | |called 260642 times, avg 0.00001s/call: # |
| | | | |251215 times (2.61571+0s) by main::matches at |
|103 | | | |line 74 of wordmatch, avg 0.00001s/call # 9427 |
| | | | |times (0.09628+0s) at line 50 of wordmatch, avg|
| | | | |0.00001s/call |
| | | | |sub debug { |
|104 |260642|0.58496 |2e-06 |my $message = shift; |
|105 | | | | |
|106 |260642|1.09917 |4e-06 |if ( $debug ) { |
|107 | | | |print STDERR "DBG: $message\n"; |
|108 | | | |} |
|109 | | | |} |
|110 | | | | |
|111 |1 |0.01501 |0.01501|exit 0; |
|112 | | | | | В нём содержится множество очень полезной информации — это, похоже, верный путь вперёд.
См. также Devel::NYTProf::Apache который подключает Devel::NYTProf к mod_perl.
СОРТИРОВКА
Модули Perl не являются единственными инструментами, которые есть у специалиста по производительности. Не следует игнорировать системные инструменты, как time , как показывает следующий пример, где мы кратко рассмотрим сортировку. Много книг, диссертаций и статей было написано об эффективных алгоритмах сортировки, и это не место для повторения такой работы. Есть несколько хороших модулей сортировки, которые тоже заслуживают внимания: Sort::Maker, Sort::Key приходят на ум. Однако всё ещё возможно сделать некоторые наблюдения относительно определённых интерпретаций Perl, касающихся задач сортировки наборов данных, и привести один или два примера того, как сортировка больших объёмов данных может повлиять на производительность. Во-первых, часто упускается из виду момент, когда сортируются большие объёмы данных, можно попытаться сократить обрабатываемый набор данных, и во многих случаях grep() может быть довольно полезным как простой фильтр:
@data = sort grep { /$filter/ } @incoming Такая команда может значительно уменьшить объём материала, который нужно сортировать в первую очередь, и её не следует игнорировать из-за простоты. Принцип KISS слишком часто упускается из виду — следующий пример использует простую системную утилиту time для демонстрации. Давайте рассмотрим фактический пример сортировки содержимого большого файла, например, файла журнала apache. Этот файл содержит более четверти миллиона строк, имеет размер 50 МБ, а фрагмент его содержимого выглядит так:
# logfile
188.209-65-87.adsl-dyn.isp.belgacom.be - - [08/Feb/2007:12:57:16 +0000] "GET /favicon.ico HTTP/1.1" 404 209 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"
188.209-65-87.adsl-dyn.isp.belgacom.be - - [08/Feb/2007:12:57:16 +0000] "GET /favicon.ico HTTP/1.1" 404 209 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"
151.56.71.198 - - [08/Feb/2007:12:57:41 +0000] "GET /suse-on-vaio.html HTTP/1.1" 200 2858 "http://www.linux-on-laptops.com/sony.html" "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1"
151.56.71.198 - - [08/Feb/2007:12:57:42 +0000] "GET /data/css HTTP/1.1" 404 206 "http://www.rfi.net/suse-on-vaio.html" "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1"
151.56.71.198 - - [08/Feb/2007:12:57:43 +0000] "GET /favicon.ico HTTP/1.1" 404 209 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1"
217.113.68.60 - - [08/Feb/2007:13:02:15 +0000] "GET / HTTP/1.1" 304 - "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"
217.113.68.60 - - [08/Feb/2007:13:02:16 +0000] "GET /data/css HTTP/1.1" 404 206 "http://www.rfi.net/" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"
debora.to.isac.cnr.it - - [08/Feb/2007:13:03:58 +0000] "GET /suse-on-vaio.html HTTP/1.1" 200 2858 "http://www.linux-on-laptops.com/sony.html" "Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.0 (like Gecko)"
debora.to.isac.cnr.it - - [08/Feb/2007:13:03:58 +0000] "GET /data/css HTTP/1.1" 404 206 "http://www.rfi.net/suse-on-vaio.html" "Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.0 (like Gecko)"
debora.to.isac.cnr.it - - [08/Feb/2007:13:03:58 +0000] "GET /favicon.ico HTTP/1.1" 404 209 "-" "Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.0 (like Gecko)"
195.24.196.99 - - [08/Feb/2007:13:26:48 +0000] "GET / HTTP/1.0" 200 3309 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.9) Gecko/20061206 Firefox/1.5.0.9"
195.24.196.99 - - [08/Feb/2007:13:26:58 +0000] "GET /data/css HTTP/1.0" 404 206 "http://www.rfi.net/" "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.9) Gecko/20061206 Firefox/1.5.0.9"
195.24.196.99 - - [08/Feb/2007:13:26:59 +0000] "GET /favicon.ico HTTP/1.0" 404 209 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.9) Gecko/20061206 Firefox/1.5.0.9"
crawl1.cosmixcorp.com - - [08/Feb/2007:13:27:57 +0000] "GET /robots.txt HTTP/1.0" 200 179 "-" "voyager/1.0"
crawl1.cosmixcorp.com - - [08/Feb/2007:13:28:25 +0000] "GET /links.html HTTP/1.0" 200 3413 "-" "voyager/1.0"
fhm226.internetdsl.tpnet.pl - - [08/Feb/2007:13:37:32 +0000] "GET /suse-on-vaio.html HTTP/1.1" 200 2858 "http://www.linux-on-laptops.com/sony.html" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"
fhm226.internetdsl.tpnet.pl - - [08/Feb/2007:13:37:34 +0000] "GET /data/css HTTP/1.1" 404 206 "http://www.rfi.net/suse-on-vaio.html" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"
80.247.140.134 - - [08/Feb/2007:13:57:35 +0000] "GET / HTTP/1.1" 200 3309 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)"
80.247.140.134 - - [08/Feb/2007:13:57:37 +0000] "GET /data/css HTTP/1.1" 404 206 "http://www.rfi.net" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)"
pop.compuscan.co.za - - [08/Feb/2007:14:10:43 +0000] "GET / HTTP/1.1" 200 3309 "-" "www.clamav.net"
livebot-207-46-98-57.search.live.com - - [08/Feb/2007:14:12:04 +0000] "GET /robots.txt HTTP/1.0" 200 179 "-" "msnbot/1.0 (+http://search.msn.com/msnbot.htm)"
livebot-207-46-98-57.search.live.com - - [08/Feb/2007:14:12:04 +0000] "GET /html/oracle.html HTTP/1.0" 404 214 "-" "msnbot/1.0 (+http://search.msn.com/msnbot.htm)"
dslb-088-064-005-154.pools.arcor-ip.net - - [08/Feb/2007:14:12:15 +0000] "GET / HTTP/1.1" 200 3309 "-" "www.clamav.net"
196.201.92.41 - - [08/Feb/2007:14:15:01 +0000] "GET / HTTP/1.1" 200 3309 "-" "MOT-L7/08.B7.DCR MIB/2.2.1 Profile/MIDP-2.0 Configuration/CLDC-1.1" Задача заключается в сортировке 286 525 строк этого файла по коду ответа, запросу, браузеру, URL-адресу обратной ссылки и, наконец, дате. Одним из решений может быть использование следующего кода, который перебирает файлы, указанные в командной строке.
# sort-apache-log
#!/usr/bin/perl -n
use strict;
use warnings;
my @data;
LINE:
while ( <> ) {
my $line = $_;
if (
$line =~ m/^(
([\w\.\-]+) # client
\s*-\s*-\s*\[
([^]]+) # date
\]\s*"\w+\s*
(\S+) # query
[^"]+"\s*
(\d+) # status
\s+\S+\s+"[^"]*"\s+"
([^"]*) # browser
"
.*
)$/x
) {
my @chunks = split(/ +/, $line);
my $ip = $1;
my $date = $2;
my $query = $3;
my $status = $4;
my $browser = $5;
push(@data, [$ip, $date, $query, $status, $browser, $line]);
}
}
my @sorted = sort {
$a->[3] cmp $b->[3]
||
$a->[2] cmp $b->[2]
||
$a->[0] cmp $b->[0]
||
$a->[1] cmp $b->[1]
||
$a->[4] cmp $b->[4]
} @data;
foreach my $data ( @sorted ) {
print $data->[5];
}
exit 0; При выполнении этой программы перенаправьте STDOUT , чтобы можно было проверить правильность вывода по результатам последующих тестов, и используйте системную утилиту time для проверки общего времени выполнения.
$> time ./sort-apache-log logfile > out-sort
real 0m17.371s
user 0m15.757s
sys 0m0.592s Программа заняла чуть более 17 секунд реального времени. Обратите внимание на разные значения, которые выводит time , важно всегда использовать одно и то же значение и не путать, что оно означает.
- Время выполнения
-
Общее или реальное время между вызовом
timeи завершением. Время выполнения включает время пользователя и время системы, а также время ожидания других пользователей и процессов в системе. Неизбежно, это наиболее приблизительное из приведённых измерений. - Время работы процессора пользователя
-
Время пользователя — это время, которое весь процесс провёл от имени пользователя в этой системе, выполняя эту программу.
- Время работы процессора системы
-
Время системы — это время, которое сам ядро потратил на выполнение процедур или системных вызовов от имени пользователя этого процесса.
Выполнение этой же задачи как Schwarzian Transform позволяет исключить массивы ввода и вывода для хранения всех данных и работать с вводом непосредственно по мере его поступления. В остальном код выглядит довольно похожим:
# sort-apache-log-schwarzian
#!/usr/bin/perl -n
use strict;
use warnings;
print
map $_->[0] =>
sort {
$a->[4] cmp $b->[4]
||
$a->[3] cmp $b->[3]
||
$a->[1] cmp $b->[1]
||
$a->[2] cmp $b->[2]
||
$a->[5] cmp $b->[5]
}
map [ $_, m/^(
([\w\.\-]+) # client
\s*-\s*-\s*\[
([^]]+) # date
\]\s*"\w+\s*
(\S+) # query
[^"]+"\s*
(\d+) # status
\s+\S+\s+"[^"]*"\s+"
([^"]*) # browser
"
.*
)$/xo ]
=> <>;
exit 0; Запустите новый код на том же файле журнала, что и выше, чтобы проверить новое время.
$> time ./sort-apache-log-schwarzian logfile > out-schwarz
real 0m9.664s
user 0m8.873s
sys 0m0.704s Время сократилось вдвое, что является достойным улучшением скорости по любым стандартам. Естественно, важно проверить, что вывод согласуется с первым запуском программы, для этого понадобится утилита Unix cksum .
$> cksum out-sort out-schwarz
3044173777 52029194 out-sort
3044173777 52029194 out-schwarz Кстати. Также будьте внимательны к давлению со стороны менеджеров, которые видят, как вы ускорили программу на 50% от времени выполнения один раз, только чтобы получить запрос через месяц сделать то же самое снова (история из жизни) – вам просто нужно будет указать, что вы только человек, даже если вы программист Perl, и вы увидите, что вы можете сделать...
ВЕДЕНИЕ ЖУРНАЛА
Неотъемлемой частью любого хорошего процесса разработки является соответствующая обработка ошибок с надлежащими информативными сообщениями, однако существует школа мысли, которая предполагает, что файлы журнала должны быть разговорчивыми, как если бы цепочка непрерывного вывода каким-то образом гарантировала выживание программы. Если скорость является проблемой, этот подход неверен.
Часто встречается код, который выглядит примерно так:
logger->debug( "A logging message via process-id: $$ INC: "
. Dumper(\%INC) ) Проблема в том, что этот код всегда будет парситься и выполняться, даже когда уровень отладки, заданный в файле конфигурации ведения журнала, равен нулю. Как только подпрограмма debug() будет запущена, и внутренняя переменная $debug будет подтверждена как нулевая, например, сообщение, которое было отправлено, будет отброшено, и программа продолжит работу. Однако в приведенном примере хеш \%INC уже будет выведен, а строка сообщения построена, все это можно обойти с помощью переменной отладки на уровне оператора, как в этом примере:
logger->debug( "A logging message via process-id: $$ INC: "
. Dumper(\%INC) ) if $DEBUG; Этот эффект можно продемонстрировать, создав тестовый скрипт с обеими формами, включая подпрограмму debug() для эмуляции типичной функциональности logger().
# ifdebug
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark;
use Data::Dumper;
my $DEBUG = 0;
sub debug {
my $msg = shift;
if ( $DEBUG ) {
print "DEBUG: $msg\n";
}
};
timethese(100000, {
'debug' => sub {
debug( "A $0 logging message via process-id: $$" . Dumper(\%INC) )
},
'ifdebug' => sub {
debug( "A $0 logging message via process-id: $$" . Dumper(\%INC) ) if $DEBUG
},
}); Давайте посмотрим, что Benchmark сделает с этим:
$> perl ifdebug
Benchmark: timing 100000 iterations of constant, sub...
ifdebug: 0 wallclock secs ( 0.01 usr + 0.00 sys = 0.01 CPU) @ 10000000.00/s (n=100000)
(warning: too few iterations for a reliable count)
debug: 14 wallclock secs (13.18 usr + 0.04 sys = 13.22 CPU) @ 7564.30/s (n=100000) В одном случае код, который делает ровно то же самое, что касается вывода любой отладочной информации, то есть ничего, занимает 14 секунд, а в другом случае код занимает одну сотую секунды. Похоже, это достаточно определённо. Используйте переменную $DEBUG ПЕРЕД вызовом подпрограммы, а не полагайтесь на умную функциональность внутри неё.
Ведение журнала, если DEBUG (константа)
Можно немного продвинуться дальше, используя константу времени компиляции DEBUG.
# ifdebug-constant
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark;
use Data::Dumper;
use constant
DEBUG => 0
;
sub debug {
if ( DEBUG ) {
my $msg = shift;
print "DEBUG: $msg\n";
}
};
timethese(100000, {
'debug' => sub {
debug( "A $0 logging message via process-id: $$" . Dumper(\%INC) )
},
'constant' => sub {
debug( "A $0 logging message via process-id: $$" . Dumper(\%INC) ) if DEBUG
},
}); Запуск этой программы даёт следующий вывод:
$> perl ifdebug-constant
Benchmark: timing 100000 iterations of constant, sub...
constant: 0 wallclock secs (-0.00 usr + 0.00 sys = -0.00 CPU) @ -7205759403792793600000.00/s (n=100000)
(warning: too few iterations for a reliable count)
sub: 14 wallclock secs (13.09 usr + 0.00 sys = 13.09 CPU) @ 7639.42/s (n=100000) Константа DEBUG превосходит даже переменную $debug, показывая время выполнения минус ноль секунд и, к тому же, генерирует сообщение "предупреждение: слишком мало итераций для надёжного подсчёта". Чтобы увидеть, что происходит на самом деле, и почему у нас было слишком мало итераций, когда мы думали, что запросили 100000, мы можем использовать очень полезную функцию B::Deparse для проверки нового кода:
$> perl -MO=Deparse ifdebug-constant
use Benchmark;
use Data::Dumper;
use constant ('DEBUG', 0);
sub debug {
use warnings;
use strict 'refs';
0;
}
use warnings;
use strict 'refs';
timethese(100000, {'sub', sub {
debug "A $0 logging message via process-id: $$" . Dumper(\%INC);
}
, 'constant', sub {
0;
}
});
ifdebug-constant syntax OK Вывод показывает, что подпрограмма constant(), которую мы тестируем, заменяется значением константы DEBUG: ноль. Строка, подлежащая проверке, была полностью оптимизирована, и вы не можете получить ничего более эффективного.
ЗАКЛЮЧИТЕЛЬНЫЕ ЗАМЕЧАНИЯ
Этот документ предоставил несколько способов определения узких мест и проверки того, улучшились ли какие-либо изменения время выполнения кода.
В заключение, помните, что в настоящее время (на момент написания документа) невозможно создать полезную программу, которая будет работать за ноль или отрицательное время, и этот основной принцип можно сформулировать как: полезные программы медленные по своему определению. Конечно, можно написать практически мгновенную программу, но она не будет делать многого. Вот очень эффективная:
$> perl -e 0 Дальнейшая оптимизация – задача для p5p.
СМОТРИТЕ ТАКЖЕ
Дополнительные материалы можно найти, используя модули и ссылки ниже.
PERLDOCS
Например: perldoc -f sort.
perlfork, perlfunc, perlretut, perlthrtut.
СТРАНИЦЫ СПРАВКИ
time.
МОДУЛИ
Конечно, здесь невозможно индивидуально продемонстрировать весь код, связанный с производительностью Perl, но вот короткий список модулей из CPAN, которые заслуживают дальнейшего внимания.
Apache::DProf
Apache::SmallProf
Benchmark
DBIx::Profile
Devel::AutoProfiler
Devel::DProf
Devel::DProfLB
Devel::FastProf
Devel::GraphVizProf
Devel::NYTProf
Devel::NYTProf::Apache
Devel::Profiler
Devel::Profile
Devel::Profit
Devel::SmallProf
Devel::WxProf
POE::Devel::Profiler
Sort::Key
Sort::Maker URL
Очень полезные онлайн-справочные материалы:
http://www.ccl4.org/~nick/P/Fast_Enough/
http://www-128.ibm.com/developerworks/library/l-optperl.html
http://perlbuzz.com/2007/11/bind-output-variables-in-dbi-for-speed-and-safety.html
http://en.wikipedia.org/wiki/Performance_analysis
http://apache.perl.org/docs/1.0/guide/performance.html
http://perlgolf.sourceforge.net/
http://www.sysarch.com/Perl/sort_paper.html АВТОР
Ричард Фоли <richard.foley@rfi.net> Авторское право (c) 2008
© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.28.3/perlperf