是否直接访问@_避免使用大型标量的内存副本?

时间:2012-09-21 18:32:29

标签: perl

因此,在最近尝试修补bug时,黑客同事告诉我,由于传递给子例程(方法)的字符串的值可能非常大,因此通过$_[1]访问它会避免内存复制。但是,我认为传递给子程序的任何值都首先被复制到@_?所以在下面的例子中是两次复制的内存?或者我在传递给方法时所犯的副本是错的吗?

sub foo {
    my $self = shift

    $_[0]    # access $str in @_ directly
    my ( $str ) = @_; # makes another copy of @_
}

sub bar {
    my $self = shift;
    my $str = 'something very large'; 

    $self->foo( $str ); #copies $str to the @_ of foo
}

这就是为什么我向作者建议允许传递标量引用,这将在传递给方法本身时避免复制(除了引用本身)。重申:是否将值传递给子例程意味着将值复制到@_

3 个答案:

答案 0 :(得分:7)

根据http://perldoc.perl.org/perlsub.html(强调是我的):

  

传入的任何参数都显示在数组@_中。因此,如果您使用两个参数调用函数,那么这些参数将存储在$_[0]$_[1]中。 数组@_是一个本地数组,但其元素是实际标量参数的别名。特别是,如果更新元素$_[0],则更新相应的参数(或者如果它不可更新则发生错误)。如果参数是调用函数时不存在的数组或散列元素,则仅在修改它时(或者如果)修改该元素或者对其进行引用。 (Perl的某些早期版本创建了元素,无论元素是否已分配给。)分配给整个数组@_会删除该别名,并且不会更新任何参数。

根据我的阅读,这似乎表明,默认情况下,@_没有复制。

虽然我承认使用的语言有点迟钝。

答案 1 :(得分:5)

是的,@_中的元素是别名。将参数传递给子例程时不会发生复制。

这意味着你可以做一些有用但令人惊讶的事情:

sub strip {
    $_[0] =~ s{^\s+}{};
    $_[0] =~ s{\s+$}{};
}

my $var = "   foo   ";
strip($var);
print $var;    # "foo"

远距离的这种行动通常是令人惊讶和危险的。用户没有迹象表明strip将修改其参数。更安全,更明显的做法是将值作为参考传递。

sub strip {
    my $ref = shift;
    $$ref =~ s{^\s+}{};
    $$ref =~ s{\s+$}{};
}

my $var = "   foo   ";
strip(\$var);
print $var;    # "foo"

这既节省了内存(只复制了引用),又可以在子例程中命名参数,并且由于它们必须传递引用,因此调用者可以知道它们的变量可能会被修改。

另一种方法是使用只读别名。这为您提供了不复制变量的内存优化,允许您命名变量,但可以防止您意外更改变量。

有几种方法可以实现这一目标,但Method::Signatures方便了。

use Method::Signatures;

func no_copy($string is alias is ro) {
    # $string is an alias to $var
    print "$string\n";

    # But it cannot be altered because $string is read-only.
    # This will throw an error.
    $string .= "bar";
}

my $var = "foo";
no_copy($var);

答案 2 :(得分:2)

$_[0]不会发生$str不会发生的复制,因为它们是同一个变量的两个不同名称。

$ perl -E'my $str; sub { say \$str == \$_[0] ?1:0 }->( $str );'
1

这是在my ( $str ) = @_;中制作副本的作业。