$1 или $& быстрее заменяет совпавшую строку с помощью s/// в Perl?

StackOverflow https://stackoverflow.com/questions/1665722

  •  13-09-2019
  •  | 
  •  

Вопрос

Какой из этих вариантов дешевле?

$_ = 'abc123def';

s/\d+/$&*2/e;
say;

s/(\d+)/$1*2/e;
say;
Это было полезно?

Решение

Управляющее резюме:вместо этого используйте 5.010 /p.Производительность $& примерно то же самое для одного матча или замены, но вся программа может пострадать от этого.Это замедление носит долгосрочный, а не локальный характер.


Вот тест с версией 5.010, которую, я подозреваю, вы используете, поскольку у вас есть say там.Обратите внимание, что в версии 5.010 есть новый /p флаг, который предоставляет ${^MATCH} переменная, которая действует как $& но только для одного экземпляра оператора сопоставления или замены.

Как и в случае с любым тестом, я сравниваю его с контрольным элементом, чтобы установить базовую линию, чтобы знать, сколько времени занимают скучные моменты.Кроме того, в этом тесте есть ловушка:ты не можешь использовать $& в коде или страдает каждая замена.Сначала запустите тест без $& суб:

use 5.010;

use Benchmark qw(cmpthese);

cmpthese(1_000_000, {
   'control' => sub { my $_ = 'abc123def'; s/\d+/246/ },
   'control-e' => sub { my $_ = 'abc123def'; s/\d+/123*2/e;  },
   '/p'      => sub { my $_ = 'abc123def'; s/\d+/${^MATCH}*2/pe },
   # '$&'      => sub { my $_ = 'abc123def'; s/\d+/$&*2/e },
   '()'      => sub { my $_ = 'abc123def'; s/(\d+)/$1*2/e },
});

На моем MacBook Air под управлением Leopard и ванильного Perl 5.10:

              Rate        /p        () control-e   control
/p         70621/s        --       -1%      -58%      -78%
()         71124/s        1%        --      -58%      -78%
control-e 168350/s      138%      137%        --      -48%
control   322581/s      357%      354%       92%        --

Обратите внимание на значительное замедление /e вариант, который я добавил просто ради смеха.

Теперь я раскомментирую $& ветку, и вижу, что всё медленнее, хотя /p Кажется, здесь Ши:

              Rate        ()        $&        /p control-e   control
()         68353/s        --       -4%       -7%      -58%      -74%
$&         70872/s        4%        --       -3%      -56%      -73%
/p         73421/s        7%        4%        --      -54%      -72%
control-e 161290/s      136%      128%      120%        --      -39%
control   262467/s      284%      270%      257%       63%        --

Это странный эталон.Если я не включу control-e sub, ситуация выглядит иначе, что демонстрирует другую концепцию бенчмаркинга:это не абсолютно, и все, что вы делаете, имеет значение для конечных результатов.В этом забеге $& выглядит немного быстрее:

            Rate      ()      /p      $& control
()       69686/s      --     -3%     -3%    -72%
/p       72098/s      3%      --     -0%    -71%
$&       72150/s      4%      0%      --    -71%
control 251256/s    261%    248%    248%      --

Итак, я запустил его с control-e снова, и результаты немного меняются:

              Rate        ()        /p        $& control-e   control
()         68306/s        --       -3%       -4%      -55%      -74%
/p         70175/s        3%        --       -1%      -54%      -73%
$&         71023/s        4%        1%        --      -53%      -73%
control-e 151976/s      122%      117%      114%        --      -41%
control   258398/s      278%      268%      264%       70%        --

Разница в скорости в каждом из них также не впечатляет.Все, что ниже 7%, не так уж важно, поскольку эта разница возникает из-за накопления ошибок из-за повторных вызовов подпрограммы (попробуйте когда-нибудь, сравнив тот же код с самим собой).Небольшие различия, которые вы видите, связаны исключительно с инфраструктурой сравнительного анализа.Учитывая эти цифры, каждая техника практически одинакова по скорости.Вы не можете просто запустить тест один раз.Вам придется запустить его несколько раз, чтобы увидеть, получите ли вы повторяемые результаты.

Обратите внимание, что, хотя /p выглядит немного медленнее, но еще и медленнее, потому что $& обманывает, портя всех.Обратите также внимание на замедление управления.Это одна из причин того, что бенчмаркинг настолько опасен.Вы можете легко ввести себя в заблуждение результатами, если не задумаетесь над тем, почему они неверны (см. Освоение Perl, где я посвящаю этому целую главу.)

Этот простой и наивный тест исключает убийственную особенность $&, хотя.Давайте изменим тест для обработки дополнительного совпадения.Во-первых, базовый уровень без $& эффектов, где я построил ситуацию, в которой $& придется скопировать около 1000 символов в дополнительный оператор сопоставления:

use 5.010;

use Benchmark qw(cmpthese);

$main::long = ( 'a' x 1_000 ) . '123' . ( 'b' x 1_000 );

cmpthese(1_000_000, {
   'control' => sub { my $_ = 'abc123def'; s/\d+/246/; $main::long =~ m/^a+123/; },
   'control-e' => sub { my $_ = 'abc123def'; s/\d+/123*2/e; $main::long =~ m/^a+123/; },
   '/p'      => sub { my $_ = 'abc123def'; s/\d+/${^MATCH}*2/pe; $main::long =~ m/^a+123/; },
   #'$&'      => sub { my $_ = 'abc123def'; s/\d+/$&*2/e; $main::long =~ m/^a+123/;},
   '()'      => sub { my $_ = 'abc123def'; s/(\d+)/$1*2/e; $main::long =~ m/^a+123/; },
});

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

              Rate        ()        /p control-e   control
()         52826/s        --       -4%      -49%      -63%
/p         54885/s        4%        --      -47%      -61%
control-e 103734/s       96%       89%        --      -27%
control   141243/s      167%      157%       36%        --

Теперь я раскомментирую $& суб:

              Rate        ()        $&        /p control-e   control
()         50607/s        --       -1%       -3%      -43%      -59%
$&         50968/s        1%        --       -2%      -43%      -58%
/p         52274/s        3%        3%        --      -41%      -57%
control-e  89206/s       76%       75%       71%        --      -27%
control   122100/s      141%      140%      134%       37%        --

Этот результат очень интересен.Сейчас /p, все еще наказан за мошенничество $&, немного быстрее (хотя всё равно в пределах шума), хотя существенно страдают все.

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

Другие советы

От perldoc perlvar:

  • $MATCH
  • $&

Строка, соответствующая последнему успешному совпадению с шаблоном (не считая совпадений, скрытых внутри BLOCK или eval() окруженный нынешним BLOCK).(Мнемоника:например & в некоторых редакторах.) Эта переменная доступна только для чтения и динамически ограничивается текущей областью действия. BLOCK.

Использование этой переменной в любом месте программы приводит к значительному снижению производительности при всех совпадениях с регулярными выражениями.Видеть "ЖУКИ".

Видеть "@-" на замену.

Даже если эта информация не была удобно указана в документации, вы все равно можете засечь ее самостоятельно и выяснить это.

Вот простой способ получить представление о влиянии на производительность использования $&.Прежде всего вам необходимо создать два эталонных сценария.У них будет большая часть общего кода:

#!/usr/bin/perl

use strict;
use warnings;
use autodie;
use File::Spec::Functions qw( devnull );

open my $output, '>', devnull;

my $str = <<EO_LIPSUM;
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
EO_LIPSUM

use Benchmark qw( timethese );

Для первого теста добавьте

### benchmark with $MATCH

timethese -1, {
    match_var => sub {
        $str =~ /commodo/;
        print $output $&;
        $str =~ /^Lorem|ipsum/ and print $output 'yes';
    }
}

а для второго эталонного файла используйте

timethese -1, {
    capture => sub {
        $str =~ /(commodo)/;
        print $output $1;
        $str =~ /^Lorem|ipsum/ and print $output 'yes';
    }
}

Теперь давайте запустим эти тесты (они должен быть в отдельных файлах):

Benchmark: running capture for at least 1 CPU seconds...
   capture:  1 wallclock secs ( 1.05 usr +  0.00 sys =  1.05 CPU) @ 301485.20/s
(n=315655)
Benchmark: running match_var for at least 1 CPU seconds...
 match_var:  1 wallclock secs ( 1.22 usr +  0.02 sys =  1.23 CPU) @ 255591.09/s
(n=315655)

То есть, используя $& в данном случае вызвало замедление примерно на 15%.Замедление вызвано влиянием $& при простом совпадении регулярного выражения.Без

$str =~ /^Lorem|ipsum/ and print $output 'yes';

линия, версия с $& на самом деле работает быстрее.

use Benchmark;

и тест.

В общем – действительно, правда, это не имеет значения.Если только вы не выполняете миллиарды таких операций.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top