对于子程序参数,Perl的移位与来自@_的赋值之间是否存在差异?

时间:2009-01-06 02:58:39

标签: perl parameters subroutine

让我们暂时忽略Damian Conway对任何给定子程序不超过三个位置参数的最佳实践。

以下两个示例在性能或功能方面是否有任何区别?

使用shift

sub do_something_fantastical {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;
}

使用@_

sub do_something_fantastical {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
}

如果两个示例在性能和功能方面相同,那么人们对一种格式的看法是什么?显然,使用@_的示例代码行较少,但使用shift并不是更易读,如另一个示例所示?欢迎有充分理由的意见。

9 个答案:

答案 0 :(得分:28)

有功能差异。 shift会修改@_@_的分配则不会。如果您之后不需要使用@_,那么这种差异可能对您无关紧要。我尝试始终使用列表分配,但有时我会使用shift

但是,如果我从shift开始,就像这样:

 my( $param ) = shift;

我经常创建这个错误:

 my( $param, $other_param ) = shift;

那是因为我经常不使用shift,所以我忘了转到作业的右侧,将其更改为@_。这是不使用shift的最佳做法。我可以像你在你的例子中那样为每个shift制作单独的行,但这只是单调乏味。

答案 1 :(得分:16)

至少在我的系统上,它似乎取决于Perl和架构的版本:

#!/usr/bin/perl -w
use strict;
use warnings;
use autodie;

use Benchmark qw( cmpthese );

print "Using Perl $] under $^O\n\n";

cmpthese(
    -1,
    {
        shifted   => 'call( \&shifted )',
        list_copy => 'call( \&list_copy )',
    }
);

sub call {
    $_[0]->(1..6);  # Call our sub with six dummy args.
}

sub shifted {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;

    return;
}

sub list_copy {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
    return;
}

结果:

Using Perl 5.008008 under cygwin

              Rate   shifted list_copy
shifted   492062/s        --      -10%
list_copy 547589/s       11%        --


Using Perl 5.010000 under MSWin32

              Rate list_copy   shifted
list_copy 416767/s        --       -5%
shifted   436906/s        5%        --


Using Perl 5.008008 under MSWin32

              Rate   shifted list_copy
shifted   456435/s        --      -19%
list_copy 563106/s       23%        --

Using Perl 5.008008 under linux

              Rate   shifted list_copy
shifted   330830/s        --      -17%
list_copy 398222/s       20%        --

所以看起来list_copy通常比移位快20%,除了Perl 5.10,其中移位实际上稍快一点!

请注意,这些是快速得出的结果。实际速度差异大于此处列出的,因为Benchmark还会计算调用和返回子程序所需的时间,这将对结果产生调节作用。我没有做过任何调查,看看Perl是否正在进行任何特殊的优化。您的里程可能会有所不同。

答案 2 :(得分:8)

我认为移位示例比使用@_慢,因为它是6个函数调用而不是1.它是否显着或甚至可测量是一个不同的问题。将每个循环放入10k迭代循环中并计时。

至于美学,我更喜欢@_方法。使用带有意外剪切和粘贴的移位方法似乎太容易弄乱变量的顺序。另外,我见过很多人做过这样的事情:

sub do_something {
   my $foo = shift;
   $foo .= ".1";

   my $baz = shift;
   $baz .= ".bak";

   my $bar = shift;
   $bar .= ".a";
}

这个,恕我直言,非常讨厌,很容易导致错误,例如如果你切割baz块并将其粘贴在条块下面。我只是在变量附近定义变量,但我认为在函数顶部定义传入的变量优先。

答案 3 :(得分:4)

通常我会使用第一个版本。这是因为我通常需要与移位一起进行错误检查,这更容易编写。说,

sub do_something_fantastical {
    my $foo   = shift || die("must have foo");
    my $bar   = shift || 0;  # $bar is optional
    # ...
}

答案 4 :(得分:3)

我更喜欢将@_解压缩为列表(您的第二个示例)。虽然像Perl中的所有内容一样,但有些情况下使用shift会很有用。例如,用于在基类中重写的passthru方法,但是如果不覆盖它们,则要确保事物仍然有效。


package My::Base;
use Moose;
sub override_me { shift; return @_; }

答案 5 :(得分:2)

我更喜欢使用

sub do_something_fantastical {
    my ( $foo, $bar, $baz, $qux, $quux, $corge ) = @_;
}

因为它更具可读性。如果不经常调用此代码,则值得。在极少数情况下,您需要经常调用make函数,而不是直接使用@_。它仅对非常短的函数有效,并且您必须确保此函数将来不会发展(一次写入函数)。在这种情况下,我在5.8.8中进行基准测试,单个参数的移位速度比$ _ [0]快,但使用$ _ [0]和$ _ [1]的两个参数比移位,移位更快。

sub fast1 { shift->call(@_) }

sub fast2 { $_[0]->call("a", $_[1]) }

但回到你的问题。我也更喜欢以这种方式对许多参数的移位进行一行@_赋值

sub do_something_fantastical2 {
    my ( $foo, $bar, $baz, @rest ) = @_;
    ...
}

当Suspect @rest不会那么大。在其他情况下

sub raise {
    my $inc = shift;
    map {$_ + $inc} @_;
}

sub moreSpecial {
    my ($inc, $power) = (shift(), shift());
    map {($_ + $inc) ** $power} @_;
}

sub quadratic {
    my ($a, $b, $c) = splice @_, 0, 3;
    map {$a*$_*$_ + $b*$_ + $c} @_;
}

在极少数情况下我需要尾部调用优化(当然是手工)然后我必须直接使用@_,而不是短函数是值得的。

sub _switch    #(type, treeNode, transform[, params, ...])
{
    my $type = shift;
    my ( $treeNode, $transform ) = @_;
    unless ( defined $type ) {
        require Data::Dumper;
        die "Broken node: " . Data::Dumper->Dump( $treeNode, ['treeNode'] );
    }
    goto &{ $transform->{$type} }   if exists $transform->{$type};
    goto &{ $transform->{unknown} } if exists $transform->{unknown};
    die "Unknown type $type";
}

sub switchExTree    #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, $treeNode->{type};    # set type
    goto &_switch;                    # tail call
}

sub switchCompact                     #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, (%$treeNode)[0];      # set type given as first key
    goto &_switch;                    # tail call
}

sub ExTreeToCompact {
    my $tree = shift;
    return switchExTree( $tree, \%transformExTree2Compact );
}

sub CompactToExTree {
    my $tree = shift;
    return switchCompact( $tree, \%transformCompact2ExTree );
}

其中%transformExTree2Compact和%transformCompact2ExTree是key中的类型的哈希值和代码ref的值,可以尾调用switchExTree或switchCompact它自己。但这种方法很少真正需要,而且必须保持较低的价值。

总之,可读性和可维护性必须特别在perl中,并且在一行中分配@_更好。如果你想设置默认值,你可以在它之后做。

答案 6 :(得分:1)

最好的方式,恕我直言,是两者的轻微混合,如模块中的新功能:

our $Class;    
sub new {
    my $Class = shift;
    my %opts = @_;
    my $self = \%opts;
    # validate %opts for correctness
    ...
    bless $self, $Class;
}

然后,构造函数的所有调用参数都作为哈希传递,这使得代码比仅仅参数列表更具可读性。

另外,与brian said一样,@_变量未经修改,这在不寻常的情况下非常有用。

答案 7 :(得分:0)

我怀疑你是否正在做(粗略)相当于:

push @bar, shift @_ for (1 :: $big_number);

然后你做错了什么。我总是使用my ($foo, $bar) = @_;形式,因为我用足够的次数拍摄了自己的脚... ...

答案 8 :(得分:0)

我更喜欢

sub factorial{
  my($n) = @_;

  ...

}

由于简单的原因,科莫多将能够告诉我论证的内容,当我去使用它时。