Эффективен ли Perl Goatse «Секретный оператор»? - PullRequest
25 голосов
/ 22 октября 2010

«Оператор козла» или идиома =()= в Perl вызывает выражение в контексте списка.

Пример:

my $str = "5 and 4 and a 3 and 2 1 BLAST OFF!!!";
my $count =()= $str =~ /\d/g; # 5 matches...
print "There are $count numbers in your countdown...\n\n";

Когда я интерпретирую использование, вот что происходит:

  1. $str =~ /\d/g соответствует всем цифрам. Контекст переключателя и списка g создает список этих совпадений. Пусть это будет пример «List Producer», а в Perl это может быть много вещей.
  2. =()= вызывает назначение пустому списку, поэтому все фактические совпадения копируются в пустой список.
  3. Присвоение в скалярном контексте $ count списка, созданного в 2. дает счет списка или результат 5.
  4. Счетчик ссылок пустого списка =()= обнуляется после скалярного присваивания. Копия элементов списка затем удаляется Perl.

Вопросы эффективности:

  1. Я ошибаюсь, как я анализирую это?
  2. Если у вас есть какой-нибудь List Producer, и все, что вас интересует, это количество, есть ли более эффективный способ сделать это?

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

Ответы [ 3 ]

23 голосов
/ 22 октября 2010

Perl 5 умел копировать списки. Он копирует только столько предметов, сколько находится на левой стороне. Это работает, потому что назначение списка в скалярном контексте дает количество элементов с правой стороны. Таким образом, n элементы будут созданы регулярным выражением, но они не будут скопированы и удалены, а просто удалены. Вы можете увидеть разницу, которую делает дополнительная копия в простом случае, в тесте ниже.

Что касается эффективности, то итеративное решение часто проще в использовании памяти и ЦП, но это должно быть сопоставлено с краткостью оператора goatse secret. Вот результаты сравнительного анализа различных решений:

naive: 10
iterative: 10
goatse: 10

for 0 items:
               Rate iterative    goatse     naive
iterative 4365983/s        --       -7%      -12%
goatse    4711803/s        8%        --       -5%
naive     4962920/s       14%        5%        --

for 1 items:
               Rate     naive    goatse iterative
naive      749594/s        --      -32%      -69%
goatse    1103081/s       47%        --      -55%
iterative 2457599/s      228%      123%        --

for 10 items:
              Rate     naive    goatse iterative
naive      85418/s        --      -33%      -82%
goatse    127999/s       50%        --      -74%
iterative 486652/s      470%      280%        --

for 100 items:
             Rate     naive    goatse iterative
naive      9309/s        --      -31%      -83%
goatse    13524/s       45%        --      -76%
iterative 55854/s      500%      313%        --

for 1000 items:
            Rate     naive    goatse iterative
naive     1018/s        --      -31%      -82%
goatse    1478/s       45%        --      -75%
iterative 5802/s      470%      293%        --

for 10000 items:
           Rate     naive    goatse iterative
naive     101/s        --      -31%      -82%
goatse    146/s       45%        --      -75%
iterative 575/s      470%      293%        --

Вот код, который его сгенерировал:

#!/usr/bin/perl

use strict;
use warnings;

use Benchmark;

my $s = "a" x 10;

my %subs = (
    naive => sub {
        my @matches = $s =~ /a/g;
        return scalar @matches;
    },
    goatse => sub {
        my $count =()= $s =~ /a/g;
        return $count;
    },
    iterative => sub {
        my $count = 0;
        $count++ while $s =~ /a/g;
        return $count;
    },
);

for my $sub (keys %subs) {
    print "$sub: @{[$subs{$sub}()]}\n";
}

for my $n (0, 1, 10, 100, 1_000, 10_000) {
    $s = "a" x $n;
    print "\nfor $n items:\n";
    Benchmark::cmpthese -1, \%subs;
}
13 голосов
/ 22 октября 2010

В вашем конкретном примере полезен эталонный тест:

my $str = "5 and 4 and a 3 and 2 1 BLAST OFF!!!";

use Benchmark 'cmpthese';

cmpthese -2 => {
    goatse => sub {
        my $count =()= $str =~ /\d/g;
        $count == 5 or die
    },
    while => sub {
        my $count; 
        $count++ while $str =~ /\d/g;
        $count == 5 or die
    },
};

, который возвращает:

           Rate goatse  while
goatse 285288/s     --   -57%
while  661659/s   132%     --

$str =~ /\d/g в контексте списка захватывает соответствующую подстроку, даже если онане нужно.Пример while имеет регулярное выражение в скалярном (логическом) контексте, поэтому движок регулярного выражения просто должен возвращать true или false, а не фактические совпадения.

И вообще, если у вас есть функция создания спискаи заботится только о количестве предметов, написание короткой count функции происходит быстрее:

sub make_list {map {$_**2} 0 .. 1000}

sub count {scalar @_}

use Benchmark 'cmpthese';

cmpthese -2 => {
    goatse => sub {my $count =()= make_list; $count == 1001 or die},
    count  => sub {my $count = count make_list; $count == 1001 or die},
};

, что дает:

         Rate goatse  count
goatse 3889/s     --   -26%
count  5276/s    36%     --

Я думаю, почему саб быстреепотому что вызовы подпрограмм оптимизированы для передачи списков без их копирования (передаются как псевдонимы).

3 голосов
/ 22 октября 2010

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

Однако, прежде чем приступить к тестированию, самый важный вопрос - «это даже имеет значение?Профиль, прежде чем тестировать, и беспокоиться о таких вещах, только когда у вас заканчиваются реальные проблемы, которые нужно решить.:)

Если вы ищете максимальную эффективность, Perl - слишком высокий уровень.:)

...