将参数传递给 Perl 子例程时,是不是会影响数据复制性能?

Posted

技术标签:

【中文标题】将参数传递给 Perl 子例程时,是不是会影响数据复制性能?【英文标题】:Do you incur a data-copying performance hit when passing arguments to Perl subroutines?将参数传递给 Perl 子例程时,是否会影响数据复制性能? 【发布时间】:2009-06-15 21:50:32 【问题描述】:

我一直在研究几个处理大型固定宽度数据文件的 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 不会复制数据。我想这在进一步思考@_ 的混叠能力后是有道理的。参数通过引用而不是值传递。

但是,它是一种有限的按引用传递形式,因为@_ 中的引用不能直接分配给子例程中的局部变量。如第二组基准所示,此类分配确实会导致数据复制。

我理解正确吗?

【问题讨论】:

【参考方案1】:

是的,作业副本;只是传递参数没有。但是,您可以使用 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 而不是 alias 辅助函数会稍微快一些。)

【讨论】:

【参考方案2】:

IIRC,在 Perl 的“子”中,@_ 数组已经是变量的一组别名(引用)。如果修改$_[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

【讨论】:

@Igor Krivokon:正确,是的,但已经在问题中说明了,至少是隐含的。我猜“是的,你理解正确。”缺少答案。【参考方案3】:

如果你想给@_的元素起有意义的名字,你可以使用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 功能的超集;它更通用,更强大。)

【讨论】:

以上是关于将参数传递给 Perl 子例程时,是不是会影响数据复制性能?的主要内容,如果未能解决你的问题,请参考以下文章

当我将内联代码放入外部子例程时,相关区域的图像地图突出显示停止工作

将两个或多个数组传递给 Perl 子例程

将 Word.Document 传递给子例程

使用它时将参数传递给perl包

Perl 中带有输出参数的子例程的最佳实践命名约定

如何从 unix shell 调用带有参数的子例程