Есть ли разница между сдвигом Perl и назначением из @_ для параметров подпрограммы? - PullRequest
15 голосов
/ 06 января 2009

Давайте на мгновение проигнорируем лучшую практику Дамиана Конвея не более трех позиционных параметров для любой данной подпрограммы.

Есть ли разница между двумя примерами ниже в отношении производительности или функциональности?

Использование 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 ]

28 голосов
/ 06 января 2009

Есть функциональная разница. shift изменяет @_, а присвоение от @_ - нет. Если вам не нужно использовать @_ впоследствии, эта разница, вероятно, не имеет значения для вас. Я стараюсь всегда использовать назначение списка, но иногда я использую shift.

Однако, если я начну с shift, вот так:

 my( $param ) = shift;

Я часто создаю эту ошибку:

 my( $param, $other_param ) = shift;

Это потому, что я не использую shift так часто, поэтому я забываю перейти к правой стороне задания, чтобы изменить это значение на @_. В этом и заключается смысл не использовать shift. Я мог бы сделать отдельные строки для каждого shift, как вы сделали в своем примере, но это просто утомительно.

16 голосов
/ 06 января 2009

По крайней мере, в моих системах это зависит от версии 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 какую-либо специальную оптимизацию. Ваш пробег может отличаться.

Пол

8 голосов
/ 06 января 2009

Я бы предположил, что пример сдвига медленнее, чем использование @_, потому что это 6 вызовов функций вместо 1. Является ли это заметным или даже измеримым - это другой вопрос. Бросьте каждый в цикл из 10 000 итераций и рассчитайте время.

Что касается эстетики, я предпочитаю метод @_. Кажется, что было бы слишком легко испортить порядок переменных, используя метод shift со случайным вырезанием и вставкой. Кроме того, я видел, что многие люди делают что-то вроде этого:

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

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

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

Это, ИМХО, очень неприятно и может легко привести к ошибкам, например если вы обрезаете блок баз и вставляете его под блок штанг. Я все для определения переменных рядом с местом их использования, но я думаю, что определение переданных переменных в верхней части функции имеет приоритет.

4 голосов
/ 06 января 2009

Обычно я использую первую версию. Это потому, что мне обычно нужно проверять ошибки вместе со сменами, что легче написать. Скажем,

sub do_something_fantastical {
    my $foo   = shift || die("must have foo");
    my $bar   = shift || 0;  # $bar is optional
    # ...
}
3 голосов
/ 06 января 2009

Я предпочитаю распаковывать @_ как список (ваш второй пример). Хотя, как и все в Perl, есть случаи, когда использование shift может быть полезным. Например, методы passthru, которые предназначены для переопределения в базовом классе, но вы хотите убедиться, что все работает, если они не переопределены.


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

2 голосов
/ 06 января 2009

Я предпочитаю использовать

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

Потому что это более читабельно. Когда этот код не вызывается слишком часто, это стоит того. В очень редких случаях вы хотите, чтобы функция вызывалась часто, а затем напрямую использовали @_. Он эффективен только для очень коротких функций, и вы должны быть уверены, что эта функция не будет развиваться в будущем (функция Write Once). В этом случае я показал в 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 ) = @_;
    ...
}

Когда Подозреваемый @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 - это хеш-коды с ключом типа и значением ref-кода, которые могут вызывать switchExTree или switchCompact, которые он сам. Но такой подход редко действительно необходим и должен держать в затратах колледжа меньше .

В заключение, читабельность и удобство сопровождения должны быть особенно важны для perl, и лучше присваивать @_ в одном ряду. Если вы хотите установить значения по умолчанию, вы можете сделать это сразу после этого.

1 голос
/ 21 июля 2009

Лучший способ, IMHO, это небольшая смесь двух, как в новой функции в модуле:

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

Затем все вызывающие аргументы в конструктор передаются в виде хэша, что делает код гораздо более читабельным, чем просто список параметров.

Плюс, как сказал Брайан , переменная @_ не изменена, что может быть полезно в необычных случаях.

0 голосов
/ 21 июля 2009

Я предпочитаю

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

  ...

}

По той простой причине, что Комодо сможет рассказать мне, каковы аргументы, когда я начну его использовать.

0 голосов
/ 18 февраля 2009

Я подозреваю, что вы делаете (грубый) эквивалент:

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

Тогда вы делаете что-то не так. Я почти всегда использую форму my ($foo, $bar) = @_;, потому что я выстрелил себе в ногу, используя последний слишком много раз ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...