Я задался вопросом, не слишком ли много в используемых нами эталонных тестах движущихся частей: мы обрабатываем файлы данных разных размеров, используем разные длины строк и пытаемся измерить скорость tr
относительно своих конкурентов - с помощью базовые (но не проверенные) предположения, что tr
- это метод, производительность которого зависит от длины строки.
Кроме того, как отметил Брайан в нескольких комментариях, мы загружаем tr
буферы данных, которые всегда имеют одинаковый размер (4096 байт). Если какой-либо из методов должен быть нечувствительным к размеру строки, он должен быть tr
.
А потом меня поразило: что, если tr
была стабильной контрольной точкой, а другие методы - теми, которые менялись в зависимости от размера линии? Когда вы смотрите в окно своего космического корабля, это вы или та хищная птица клингон движется?
Поэтому я разработал эталонный тест, в котором размер файлов данных был постоянным: длина строки варьируется, но общее количество байтов остается неизменным. Как показывают результаты:
tr
- наименее чувствительный подход
к изменению длины линии. поскольку
общее количество обработанных байтов
постоянная для всех трех длин строк
проверено (короткое, среднее, длинное), это
означает, что tr
довольно эффективно
редактирование строки это дано. Четное
хотя файл данных короткой линии
требуется гораздо больше правок, tr
Подход может хруст данных
файл почти так же быстро, как он обрабатывает
длинный файл.
- Методы, основанные на скорости
<>
как линии становятся длиннее,
хотя с убывающей скоростью. это
имеет смысл: так как каждый вызов <>
требует некоторой работы, это должно быть
медленнее обрабатывать заданное N байтов
используя более короткие строки (по крайней мере, более
диапазон испытания).
- Подход
s///
также чувствителен
до длины строки. Как tr
, это
подход работает путем редактирования строки
это дано. Опять короткая линия
длина означает больше правок. По-видимому,
способность s///
сделать такой
правки гораздо менее эффективны, чем
это из tr
.
Вот результаты для Solaris с Perl 5.8.8:
# ln = $. <>, then check $.
# nn = $n <>, counting lines
# tr = tr/// using sysread
# ss = s/// using sysread
# S = short lines (50)
# M = medium lines (500)
# L = long lines (5000)
Rate nn-S
nn-S 1.66/s --
ln-S 1.81/s 9%
ss-S 2.45/s 48%
nn-M 4.02/s 142%
ln-M 4.07/s 145%
ln-L 4.65/s 180%
nn-L 4.65/s 180%
ss-M 5.85/s 252%
ss-L 7.04/s 324%
tr-S 7.30/s 339% # tr
tr-L 7.63/s 360% # tr
tr-M 7.69/s 363% # tr
Результаты в Perl 5.10.0 для Windows ActiveState были примерно сопоставимы.
Наконец, код:
use strict;
use warnings;
use Set::CrossProduct;
use Benchmark qw(cmpthese);
# Args: file size (in million bytes)
# N of benchmark iterations
# true/false (whether to regenerate files)
#
# My results were run with 50 10 1
main(@ARGV);
sub main {
my ($file_size, $benchmark_n, $regenerate) = @_;
$file_size *= 1000000;
my @file_names = create_files($file_size, $regenerate);
my %methods = (
ln => \&method_ln, # $.
nn => \&method_nn, # $n
tr => \&method_tr, # tr///
ss => \&method_ss, # s///
);
my $combo_iter = Set::CrossProduct->new([ [keys %methods], \@file_names ]);
open my $log_fh, '>', 'log.txt';
my %benchmark_args = map {
my ($m, $f) = @$_;
"$m-$f" => sub { $methods{$m}->($f, $log_fh) }
} $combo_iter->combinations;
cmpthese($benchmark_n, \%benchmark_args);
close $log_fh;
}
sub create_files {
my ($file_size, $regenerate) = @_;
my %line_lengths = (
S => 50,
M => 500,
L => 5000,
);
for my $f (keys %line_lengths){
next if -f $f and not $regenerate;
create_file($f, $line_lengths{$f}, $file_size);
}
return keys %line_lengths;
}
sub create_file {
my ($file_name, $line_length, $file_size) = @_;
my $n_lines = int($file_size / $line_length);
warn "Generating $file_name with $n_lines lines\n";
my $line = 'a' x ($line_length - 1);
chop $line if $^O eq 'MSWin32';
open(my $fh, '>', $file_name) or die $!;
print $fh $line, "\n" for 1 .. $n_lines;
close $fh;
}
sub method_nn {
my ($data_file, $log_fh) = @_;
open my $data_fh, '<', $data_file;
my $n = 0;
$n ++ while <$data_fh>;
print $log_fh "$data_file \$n $n\n";
close $data_fh;
}
sub method_ln {
my ($data_file, $log_fh) = @_;
open my $data_fh, '<', $data_file;
1 while <$data_fh>;
print $log_fh "$data_file \$. $.\n";
close $data_fh;
}
sub method_tr {
my ($data_file, $log_fh) = @_;
open my $data_fh, '<', $data_file;
my $n = 0;
my $buffer;
while (sysread $data_fh, $buffer, 4096) {
$n += ($buffer =~ tr/\n//);
}
print $log_fh "$data_file tr $n\n";
close $data_fh;
}
sub method_ss {
my ($data_file, $log_fh) = @_;
open my $data_fh, '<', $data_file;
my $n = 0;
my $buffer;
while (sysread $data_fh, $buffer, 4096) {
$n += ($buffer =~ s/\n//g);
}
print $log_fh "$data_file s/ $n\n";
close $data_fh;
}
Обновление в ответ на комментарий Брэда. Я перепробовал все три варианта, и они вели себя примерно так же, как s/\n//g
- медленнее для файлов данных с более короткими строками (с дополнительной квалификацией, что s/(\n)/$1/
было даже медленнее других). Интересная часть заключалась в том, что m/\n/g
была в основном той же скоростью, что и s/\n//g
, предполагая, что медлительность подхода регулярных выражений (как s///
, так и m//
) не зависит напрямую от вопроса редактирования Строка.