在perlfaq5中,有How do I count the number of lines in a file?的答案。目前的答案显示sysread
和tr/\n//
。我想尝试其他一些事情来查看tr/\n//
的速度有多快,并且还可以针对具有不同平均线长度的文件进行尝试。我创建了一个基准测试来尝试各种方法。我在Mac OS X 10.5.8和MacBook Air上的Perl 5.10.1上运行它:
wc
投降(除了短线以外最快)tr/\n//
(下一个最快的,除了长的平均线长)s/\n//g
(通常很快)while( <$fh> ) { $count++ }
(几乎总是缓慢戳戳,除非tr///
陷入困境)1 while( <$fh> ); $.
(非常快)让我们忽略wc
,即使所有的IPC内容确实都有一些有吸引力的数字。
乍一看,当行长度较小(例如100个字符)时,tr/\n//
看起来非常好,但当它们变大(一行中有1,000个字符)时,它的性能会下降。线条越长,tr/\n//
越差。我的基准测试是否有问题,或者内部是否有其他因素导致tr///
降级?为什么s///
不会同样降级?
首先,结果。:
Rate very_long_lines-tr very_long_lines-$count very_long_lines-$. very_long_lines-s very_long_lines-wc
very_long_lines-tr 1.60/s -- -10% -12% -39% -72%
very_long_lines-$count 1.78/s 11% -- -2% -32% -69%
very_long_lines-$. 1.82/s 13% 2% -- -31% -68%
very_long_lines-s 2.64/s 64% 48% 45% -- -54%
very_long_lines-wc 5.67/s 253% 218% 212% 115% --
Rate long_lines-tr long_lines-$count long_lines-$. long_lines-s long_lines-wc
long_lines-tr 9.56/s -- -5% -7% -30% -63%
long_lines-$count 10.0/s 5% -- -2% -27% -61%
long_lines-$. 10.2/s 7% 2% -- -25% -60%
long_lines-s 13.6/s 43% 36% 33% -- -47%
long_lines-wc 25.6/s 168% 156% 150% 88% --
Rate short_lines-$count short_lines-s short_lines-$. short_lines-wc short_lines-tr
short_lines-$count 60.2/s -- -7% -11% -34% -42%
short_lines-s 64.5/s 7% -- -5% -30% -38%
short_lines-$. 67.6/s 12% 5% -- -26% -35%
short_lines-wc 91.7/s 52% 42% 36% -- -12%
short_lines-tr 104/s 73% 61% 54% 14% --
Rate varied_lines-$count varied_lines-s varied_lines-$. varied_lines-tr varied_lines-wc
varied_lines-$count 48.8/s -- -6% -8% -29% -36%
varied_lines-s 51.8/s 6% -- -2% -24% -32%
varied_lines-$. 52.9/s 8% 2% -- -23% -30%
varied_lines-tr 68.5/s 40% 32% 29% -- -10%
varied_lines-wc 75.8/s 55% 46% 43% 11% --
这是基准。我确实有一个控制器,但它是如此之快我只是不打扰它。第一次运行它时,基准测试会创建测试文件并打印一些有关其行长度的统计信息:
use Benchmark qw(cmpthese);
use Statistics::Descriptive;
my @files = create_files();
open my( $outfh ), '>', 'bench-out';
foreach my $file ( @files )
{
cmpthese(
100, {
# "$file-io-control" => sub {
# open my( $fh ), '<', $file;
# print "Control found 99999 lines\n";
# },
"$file-\$count" => sub {
open my( $fh ), '<', $file;
my $count = 0;
while(<$fh>) { $count++ }
print $outfh "\$count found $count lines\n";
},
"$file-\$." => sub {
open my( $fh ), '<', $file;
1 while(<$fh>);
print $outfh "\$. found $. lines\n";
},
"$file-tr" => sub {
open my( $fh ), '<', $file;
my $lines = 0;
my $buffer;
while (sysread $fh, $buffer, 4096) {
$lines += ($buffer =~ tr/\n//);
}
print $outfh "tr found $lines lines \n";
},
"$file-s" => sub {
open my( $fh ), '<', $file;
my $lines = 0;
my $buffer;
while (sysread $fh, $buffer, 4096) {
$lines += ($buffer =~ s/\n//g);
}
print $outfh "s found $lines line\n";
},
"$file-wc" => sub {
my $lines = `wc -l $file`;
chomp( $lines );
print $outfh "wc found $lines line\n";
},
}
);
}
sub create_files
{
my @names;
my @files = (
[ qw( very_long_lines 10000 4000 5000 ) ],
[ qw( long_lines 10000 700 800 ) ],
[ qw( short_lines 10000 60 80 ) ],
[ qw( varied_lines 10000 10 200 ) ],
);
foreach my $tuple ( @files )
{
push @names, $tuple->[0];
next if -e $tuple->[0];
my $stats = create_file( @$tuple );
printf "%10s: %5.2f %5.f \n", $tuple->[0], $stats->mean, sqrt( $stats->variance );
}
return @names;
}
sub create_file
{
my( $name, $lines, $min, $max ) = @_;
my $stats = Statistics::Descriptive::Full->new();
open my( $fh ), '>', $name or die "Could not open $name: $!\n";
foreach ( 1 .. $lines )
{
my $line_length = $min + int rand( $max - $min );
$stats->add_data( $line_length );
print $fh 'a' x $line_length, "\n";
}
return $stats;
}
答案 0 :(得分:9)
我想知道我们使用的基准测试是否有太多可移动的部分:我们正在使用不同的线路长度来处理不同大小的数据文件,并试图衡量tr
相对于竞争对手的速度 - - 使用潜在的(但未经测试的)假设tr
是其性能随线长度变化的方法。
另外,正如brian在一些评论中指出的那样,我们正在为tr
缓冲区提供始终大小相同的数据(4096字节)。如果任何方法对行大小不敏感,则应为tr
。
然后它让我感到震惊:如果tr
是稳定的参考点而其他方法是随线大小变化的那些方法会怎么样?当你看到你的太空船窗口时,你或那个正在移动的克林贡鸟类?
所以我开发了一个基准,它保持数据文件的大小不变:行长度不同,但总字节数保持不变。结果显示:
tr
是最不敏感的方法
线长的变化。以来
处理的总N个字节数
所有三个线路长度都是常数
测试(短,中,长),这个
意味着tr
非常有效率
编辑它给出的字符串。甚至
虽然是短线数据文件
需要更多编辑,tr
方法能够处理数据
文件几乎和处理文件一样快
长行文件。<>
速度的方法
随着线条变长,
虽然递减率很低。这个
有道理:因为每次拨打<>
需要一些工作,应该是
更慢地处理给定的N个字节
使用较短的线(至少超过
测试范围)。s///
方法也很敏感
线长。就像tr
一样
方法通过编辑字符串来工作
给出了。再次,更短的线
长度意味着更多编辑。显然,
s///
这样做的能力
编辑效率低于
那是tr
。以下是使用Perl 5.8.8的Solaris上的结果:
# 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
Windows ActiveState的Perl 5.10.0上的结果大致相当。
最后,代码:
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;
}
更新以回应Brad的评论。我尝试了所有三种变体,它们的行为大致类似于s/\n//g
- 对于行数较短的数据文件来说速度较慢(附加资格为{ {1}}甚至比其他人慢。)有趣的是,s/(\n)/$1/
基本上与m/\n/g
的速度基本相同,这表明正则表达式方法的缓慢(s/\n//g
和s///
)并不直接取决于编辑字符串的问题。
答案 1 :(得分:2)
我也看到tr///
随着线长的增加而变得相对较慢,尽管效果并不那么显着。这些结果来自Windows 7 x64上的ActivePerl 5.10.1(32位)。我还得到“100次迭代以获得可靠的计数”警告,因此我将迭代次数提高到500次。
VL: 4501.06 288
LO: 749.25 29
SH: 69.38 6
VA: 104.66 55
Rate VL-$count VL-$. VL-tr VL-s VL-wc
VL-$count 2.82/s -- -0% -52% -56% -99%
VL-$. 2.83/s 0% -- -51% -56% -99%
VL-tr 5.83/s 107% 106% -- -10% -99%
VL-s 6.45/s 129% 128% 11% -- -99%
VL-wc 501/s 17655% 17602% 8490% 7656% --
Rate LO-$count LO-$. LO-s LO-tr LO-wc
LO-$count 16.5/s -- -1% -50% -51% -97%
LO-$. 16.8/s 1% -- -50% -51% -97%
LO-s 33.2/s 101% 98% -- -3% -94%
LO-tr 34.1/s 106% 103% 3% -- -94%
LO-wc 583/s 3424% 3374% 1655% 1609% --
Rate SH-$count SH-$. SH-s SH-tr SH-wc
SH-$count 120/s -- -7% -31% -67% -81%
SH-$. 129/s 7% -- -26% -65% -80%
SH-s 174/s 45% 35% -- -52% -73%
SH-tr 364/s 202% 182% 109% -- -43%
SH-wc 642/s 433% 397% 269% 76% --
Rate VA-$count VA-$. VA-s VA-tr VA-wc
VA-$count 92.6/s -- -5% -36% -63% -79%
VA-$. 97.4/s 5% -- -33% -61% -78%
VA-s 146/s 57% 50% -- -42% -67%
VA-tr 252/s 172% 159% 73% -- -43%
VA-wc 439/s 374% 351% 201% 74% --
编辑:我做了一个修订后的基准测试,以比较不同线路长度的费率。它清楚地表明,tr///
开始时有一个很大的优势,短线随着线条长度的增加而迅速消失。至于为什么会发生这种情况,我只能推测tr///
是针对短字符串进行优化的。
Line count rate comparison http://img69.imageshack.us/img69/6250/linecount.th.png
答案 2 :(得分:-1)
长线比短线大约65倍,而且你的数字表明tr / \ n //运行速度要慢65倍。这是预期的。
显然,对于长线来说,wc可以更好地扩展。我真的不知道为什么;也许是因为它被调整为仅计算换行符,尤其是当您使用-l
选项时。