我已经提出以下内容来检查换行器的$ scaler的最终字符:
if( $buffer !~ /\n$/ ) {
if( substr( $buffer, -1, 1 ) !~ /\n/ ) {
if( substr( $buffer, -1, 1 ) ne '\n' ) {
我能用更快的方法吗? $ buffer标量的大小可能会变大,我注意到它越大,这些条件运行的时间就越长。我确实有另一个包含$ buffer长度的标量,如果有帮助的话。
由于
完整代码:
#!/usr/bin/perl
use strict;
use warnings;
use Fcntl qw();
use Time::HiRes qw( gettimeofday tv_interval );
use constant BUFSIZE => 2 ** 21; # 2MB worked best for me, YMMV.
die "ERROR: Missing filename" if( !$ARGV[0] );
my $top = [gettimeofday];
sysopen( my $fh, $ARGV[0], Fcntl::O_RDONLY | Fcntl::O_BINARY ) or
die "ERROR: Unable to open $ARGV[0], because $!\n";
open my $output, ">", "/dev/null"; # for 'dummy' processing
my $size = -s $ARGV[0];
my $osiz = $size;
my( $buffer, $offset, $lnCtr ) = ( "", "", 0 );
while( $size ) {
my $read = sysread( $fh, $buffer, BUFSIZE, length($offset) );
$size -= $read;
my @lines = split /\n/, $buffer;
if( substr( $buffer, -1, 1 ) ne "\n" ) {
$offset = pop( @lines );
} else {
$offset = "";
}
for my $line ( @lines ) {
processLine( \$line );
$lnCtr++;
}
$buffer = $offset if( $offset );
}
close $fh;
print "Processed $lnCtr lines ($osiz bytes) in file: $ARGV[0] in ".
tv_interval( $top ).
" secs.\n";
print "Using a buffered read of ".BUFSIZE." bytes. - JLB\n";
sub processLine {
if( ref($_[0]) ) {
print $output ${$_[0]}."\n";
} else {
print $output $_[0]."\n";
}
return 0;
}
我认为我已经达到了“减少收益点”的努力,试图让这次运行更快。现在似乎能够以我的RAID5 SSD能够读取数据的速度读取数据。正如您所看到的,有一个原因我没有使用chomp(),输入可以包含数十万个换行符,我需要保留它以便能够打破行进行处理。
./fastread.pl newdata.log 处理文件中的516670行(106642635字节):newdata.log,0.674738秒。 使用2097152字节的缓冲读取。 - JLB
答案 0 :(得分:3)
Perl有两种字符串存储格式。
其中一种格式使用相同数量的字节(1)来存储字符串可以包含的每个可能字符。因此,由于Perl会跟踪字符串使用的字节数,因此substr($x, -1)
对此格式的字符串的性能不依赖于字符串的长度。
上述格式的问题在于它只能存储非常有限的字符范围。它可用于存储Unicode代码点“Eric”和“Éric”,但不能用于“Ελλάδα”。必要时(甚至在没有必要时),Perl会自动将字符串的存储格式切换为其他格式。
第二种格式可以将任何Unicode代码点存储为字符。实际上,它可以存储任何32位或64位值(取决于perl
的构建设置)。缺点是可变数量的字节用于存储每个字符。因此,即使Perl知道整个字符串使用的字节数,它也不会知道第一个字符的起始位置。*要查找最后一个字符,它必须扫描整个字符串。
也就是说,由于存储格式的属性,实际上很容易在常量时间内找到字符串的最后一个字符。
use Inline C => <<'__END_OF_C__';
# O(1) version of substr($x,-1)
SV* last_char(SV* sv) {
STRLEN len;
const char* s = SvPV(sv, len);
if (!len)
return newSVpvn("", 0);
{
const U32 utf8 = SvUTF8(sv);
const char* p = s+len-1;
if (utf8) {
while (p != s && (*p & 0xC0) != 0xC0)
--p;
}
return newSVpvn_utf8(p, s+len-p, utf8);
}
}
__END_OF_C__
* - 它确实保留了一对char位置到字节位置映射的缓存。
您已经显示了可以清理的代码,因此您甚至无需检查新行的最后一个字符。
sub processLine {
print $_[0] $_[1];
}
open(my $fh, '<:raw', $ARGV[0])
or die("Can't open $ARGV[0]: $!\n");
my $buffer = '';
my $lnCtr = 0;
while (1) {
my $rv = sysread($fh, $buffer, BUFSIZE, length($buffer));
die $! if !defined($rv);
last if !$rv;
while ($buffer =~ s/(.*\n)//) {
processLine($1);
++$lnCtr;
}
}
if (length($buffer)) {
processLine($output, $buffer);
++$lnCtr;
}
注意:
sysopen
。 open
更简单。$buffer
传递给sysread
,则使用length($offset)
没有意义。$offset
及其复制完全没必要。processLine
不需要换行符,请改用s/(.*)\n//
。答案 1 :(得分:1)
你为什么关心速度?这段代码是否在程序的一部分中是非常缓慢的,或许可以用Devel :: NYTProf进行分析?如果没有,那么我建议你选择最清楚的和最惯用的,这可能是
if( $buffer !~ /\n$/ )
您的最终版本:
if( substr( $buffer, -1, 1 ) ne '\n' )
也是一个很好的选择,除了你单引号换行,因此给你一个由反斜杠和小写n组成的双字符串。也许你来自C,其中单个字符是单引号,字符串是双引号?你想要
if( substr( $buffer, -1, 1 ) ne "\n" )
此版本
if( substr( $buffer, -1, 1 ) !~ /\n/ )
正在进行一个不应该的正则表达式匹配,因为它正在检查单字符正则表达式的单字符串。下一个阅读代码的人会认为这很奇怪并且想知道你为什么这么做。另外,回到那个速度的东西,将字符串与正则表达式匹配比仅仅比较单个字符的相等性要慢。
答案 2 :(得分:1)
这是一个基准:
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark qw(:all);
my $buffer = 'abc'x10_000_000;
$buffer .= "\n";
my $count = -2;
cmpthese($count, {
'regex' => sub {
if ($buffer !~ /\n$/) { }
},
'substr + regex' => sub {
if (substr($buffer, -1, 1) !~ /\n$/) { }
},
'substr + ne' => sub {
if (substr($buffer, -1, 1) ne "\n") { }
},
'chomp' => sub {
if (chomp $buffer) { }
},
});
<强>输出:强>
Rate substr + regex substr + ne regex chomp
substr + regex 6302468/s -- -11% -44% -70%
substr + ne 7072032/s 12% -- -37% -66%
regex 11294695/s 79% 60% -- -46%
chomp 20910531/s 232% 196% 85% --
chomp
肯定是最快的方式。
答案 3 :(得分:0)
我怀疑perl将字符串视为utf-8,并且由于某种原因必须迭代整个字符串。
您可以暂时切换到字节语义,以查看末尾的char是否为换行符。
请参阅Perl bytes pragma和perlunicode的文档。
答案 4 :(得分:0)
您可以尝试chomp。 Chomp将返回从一行末尾删除的EOL字符数:
if ( chomp $buffer ) {
print "You had an LF on the end of \$buffer";
}
当然,chomp会删除它计算的NL字符。