我一直致力于处理大型固定宽度数据文件的几个Perl脚本,从每个数据记录中提取小的子字符串。我曾经想过,将子串的提取委托给方法调用会很昂贵,因为将数据记录复制到@_数组的开销很大。所以我运行以下命令来比较(a)直接调用substr(),(b)方法调用将数据记录作为字符串传递,以及(c)方法调用通过引用传递数据记录。
use strict;
use warnings;
use Benchmark qw(timethese);
my $RECORD = '0' x 50000;
my $direct = sub { my $v = substr( $RECORD, $_, 1) for 0..999 };
my $byVal = sub { my $v = ByVal ( $RECORD, $_) for 0..999 };
my $byRef = sub { my $v = ByRef (\$RECORD, $_) for 0..999 };
sub ByVal { return substr( $_[0], $_[1], 1) }
sub ByRef { return substr(${$_[0]}, $_[1], 1) }
timethese( 10000, {
direct => $direct,
byVal => $byVal,
byRef => $byRef,
} );
my $byVal2loc = sub { my $v = ByVal2loc( $RECORD, $_) for 0..999 };
my $byRef2loc = sub { my $v = ByRef2loc(\$RECORD, $_) for 0..999 };
sub ByVal2loc { my $arg = shift; return substr( $arg, $_[0], 1) }
sub ByRef2loc { my $arg = shift; return substr( $$arg, $_[0], 1) }
timethese( $ARGV[0], {
byVal2loc => $byVal2loc,
byRef2loc => $byRef2loc,
} );
# Produces this output:
Benchmark: timing 10000 iterations of byRef, byVal, direct...
byRef: 19 wallclock secs...
byVal: 15 wallclock secs...
direct: 4 wallclock secs...
Benchmark: timing 10000 iterations of byRef2loc, byVal2loc...
byRef2loc: 21 wallclock secs...
byVal2loc: 119 wallclock secs...
正如预期的那样,直接方法是最快的。但是,我很惊讶地发现没有与我想象的“复制数据”相关的惩罚。即使我将记录的宽度增加到奇怪的比例(例如,十亿个字符),按值和按参考的基准也基本相同。
似乎在将参数传递给方法时,Perl不会复制数据。我想这有助于进一步反思@_的混叠能力。参数通过引用而不是值传递。
但是,它是一种有限形式的引用传递,因为@_中的引用不能直接分配给子例程中的局部变量。这种分配确实会导致数据复制,如第二组基准所示。
我是否正确理解了这一点?
答案 0 :(得分:8)
是的,作业复制;只是传递参数不。但是,您可以使用Lexical::Alias将词法替换为@_中的元素。这个修改后的基准测试显示,使用引用的速度是第三快,但无论$ RECORD的长度如何都是如此:
use strict;
use warnings;
use Benchmark qw(timethese);
use Lexical::Alias;
my $RECORD = '0' x 5000000;
my $byVal2loc = sub { my $v = ByVal2loc( $RECORD, $_) for 0..999 };
my $byRef2loc = sub { my $v = ByRef2loc(\$RECORD, $_) for 0..999 };
my $byAlias2loc = sub { my $v = ByAlias2loc( $RECORD, $_ ) for 0..999 };
sub ByVal2loc { my $arg = shift; return substr( $arg, $_[0], 1) }
sub ByRef2loc { my $arg = shift; return substr( $$arg, $_[0], 1) }
sub ByAlias2loc { my $arg; alias($_[0], $arg); return substr( $arg, $_[0], 1 ) }
timethese( $ARGV[0], {
byVal2loc => $byVal2loc,
byRef2loc => $byRef2loc,
byAlias2loc => $byAlias2loc,
} );
# output:
Benchmark: running byAlias2loc, byRef2loc, byVal2loc for at least 3 CPU seconds...
byAlias2loc: 3 wallclock secs ( 3.16 usr + 0.00 sys = 3.16 CPU) @ 430.70/s (n=1361)
byRef2loc: 4 wallclock secs ( 3.24 usr + 0.00 sys = 3.24 CPU) @ 1329.63/s (n=4308)
byVal2loc: 5 wallclock secs ( 4.95 usr + 0.01 sys = 4.96 CPU) @ 0.40/s (n=2)
(warning: too few iterations for a reliable count)
(直接使用alias_r而不是别名辅助函数稍快一点。)
答案 1 :(得分:6)
IIRC,在Perl'sub'中,@_
数组已经是变量的一组别名(引用)。如果修改$_[0]
,则会影响调用函数中的变量。
#!/bin/perl -w
use strict;
sub x
{
print "x = $_[0]\n";
$_[0] = "pinkerton";
print "x = $_[0]\n";
}
my $y = "abc";
print "y = $y\n";
x($y);
print "y = $y\n";
输出结果为:
y = abc
x = abc
x = pinkerton
y = pinkerton
答案 2 :(得分:0)
如果您想提供@_有意义名称的元素,可以使用Data::Alias为它们创建别名,所以
use Data::Alias;
sub foo {
alias my ($a, $b, $c) = @_;
}
你可以在阵列和散列中做类似的别名。
alias my ($a, $b, @c) = @_;
alias my ($a, $b, %c) = @_;
实际上,别名为哈希
alias my (%p) = @_;
特别强大,因为它提供了传递引用的命名参数。好的。
(Data :: Alias提供了Lexical :: Alias功能的超集;它更通用,功能更强大。)