Является Perl unpack() быстрее, чем substr()?

Несколько раз я читал, что unpack() быстрее, чем substr(), особенно по мере увеличения количества подстрок. Тем не менее, этот критерий предполагает иное. Является ли мой критерий ошибочным или является предположительным преимуществом производительности unpack() удержания из старых версий Perl?

use strict;
use warnings;
use Benchmark;

my ($data, $format_string, $n_substrings);

my %methods = (
    unpack => sub { return unpack $format_string, $data },
    substr => sub { return map {substr $data, $_, 1} 0 .. $n_substrings - 1 },
);

for my $exp (1 .. 5){
    $n_substrings = 10 ** $exp;
    print $n_substrings, "\n";
    $format_string = 'a1' x $n_substrings;
    $data          =   9  x $n_substrings;
    Benchmark::cmpthese -2, \%methods;
}

Выход (в Windows):

10
           Rate unpack substr
unpack 131588/s     --   -52%
substr 276802/s   110%     --
100
          Rate unpack substr
unpack 13660/s     --   -57%
substr 31636/s   132%     --
1000
         Rate unpack substr
unpack 1027/s     --   -68%
substr 3166/s   208%     --
10000
         Rate unpack substr
unpack 84.4/s     --   -74%
substr  322/s   281%     --
100000
         Rate unpack substr
unpack 5.46/s     --   -82%
substr 30.1/s   452%     --

Как указано в некоторых ответах, unpack() плохо работает в Windows. Здесь выход на машине солярия - не так уж и решающий, но substr() по-прежнему выигрывает гонка стопы:

10
           Rate unpack substr
unpack 202274/s     --    -4%
substr 210818/s     4%     --
100
          Rate unpack substr
unpack 22015/s     --    -9%
substr 24322/s    10%     --
1000
         Rate unpack substr
unpack 2259/s     --    -9%
substr 2481/s    10%     --
10000
        Rate unpack substr
unpack 225/s     --    -9%
substr 247/s     9%     --
100000
         Rate unpack substr
unpack 22.0/s     --   -10%
substr 24.4/s    11%     --

Ответы

Ответ 1

Спрашивая этот вопрос, я несколько раз тестировал substr против unpack в разных условиях. Вот несколько вещей, которые я узнал:

  • Не устанавливайте бенчмарк таким образом который вызывает функции Perl в void контекста (как в моем первоначальном вопросе, см. полезный ответ от dlowe). Некоторые функции Perl имеют оптимизации, когда они вызываются в контекст void (и эти оптимизации по-видимому, зависит от ОС), потенциально искажая результаты бенчмаркинга.

  • Если ваше использование substr включает цикл (например, повторение список расположения столбцов), unpack всегда быстрее. Однако кажущаяся медлительность substr в этом ситуация обусловлена ​​накладными расходами цикл, а не substr.

  • Если требуется несколько полей, substr обычно быстрее или быстро, как unpack.

  • Если более нескольких полей требуемые, сопоставление между головами и головами между unpack и эквивалентным количество вызовов substr не меняется так как количество полей увеличивается: оба подхода становятся медленнее с той же скоростью.

  • Результаты могут варьироваться в зависимости от операционной системы. На моей машине с Windows XP unpack имел небольшое преимущество, когда потребовалось несколько полей. На нашей Машины Solaris на моем рабочем месте, substr всегда был быстрее, даже в сотни полей.

Нижняя строка: производительность unpack vs. substr не является очень большой проблемой, независимо от количества полей. Используйте любой подход, чтобы получить самый четкий код. Однако если вы обнаружите, что используете substr в конструкции цикла, переход на unpack приведет к заметному увеличению скорости.

Ответ 2

На самом деле, ваш бенчмарк ошибочен, по-настоящему, действительно интересен, но то, что он сводит к минимуму, состоит в том, что то, что вы действительно сравниваете, - это относительная эффективность, с которой распаковка и карта могут отбросить список, потому что Benchmark:: cmpthese() выполняет функции в контексте void.

Причина, по которой ваш субстрат выходит сверху, - это строка кода в pp_ctl.c pp_mapwhile():

if (items && gimme != G_VOID) {

то есть. perl-карта волшебным образом пропускает кучу работы (а именно, распределяет память для результатов карты), если она знает, что она вызывается в контексте void!

(Моя догадка на окнах по сравнению с другими, описанными выше, заключается в том, что распределение памяти perl на базе Windows ужасно, поэтому пропускать выделение - это большая экономия там - просто догадка, хотя у меня нет окна играть с. Но фактическая реализация распаковки - это прямой код C и не должна существенно отличаться от окон к другим.)

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

  • назначить список массиву
  • цикл над списком внутри функции и вернуть ничего
  • возвращает ссылку на список (скрывая контекст void)

Здесь моя версия% методов со всеми тремя версиями:

my %methods = (
    unpack_assign => sub { my @foo = unpack $format_string, $data; return },
    unpack_loop => sub { for my $foo (unpack $format_string, $data) { } },
    unpack_return_ref => sub { return [ unpack $format_string, $data ] },
    unpack_return_array => sub { return unpack $format_string, $data },

    substr_assign => sub { my @foo = map {substr $data, $_, 1} 0 .. ($n_substrings - 1) },
    substr_loop => sub { for my $foo ( map {substr $data, $_, 1} 0 .. ($n_substrings - 1)) { } },
    substr_return_ref => sub { return [ map {substr $data, $_, 1} 0 .. ($n_substrings - 1) ] },
    substr_return_array => sub { return map { substr $data, $_, 1} 0 .. ($n_substrings - 1) },
);

И мои результаты:

$ perl -v

This is perl, v5.10.0 built for x86_64-linux-gnu-thread-multi

$ perl foo.pl
10
                        Rate substr_assign substr_return_ref substr_loop unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       101915/s            --              -20%        -21%          -28%              -51%        -51%                -65%                -69%
substr_return_ref   127224/s           25%                --         -1%          -10%              -39%        -39%                -57%                -62%
substr_loop         128484/s           26%                1%          --           -9%              -38%        -39%                -56%                -61%
unpack_assign       141499/s           39%               11%         10%            --              -32%        -32%                -52%                -57%
unpack_return_ref   207144/s          103%               63%         61%           46%                --         -1%                -29%                -37%
unpack_loop         209520/s          106%               65%         63%           48%                1%          --                -28%                -37%
unpack_return_array 292713/s          187%              130%        128%          107%               41%         40%                  --                -12%
substr_return_array 330827/s          225%              160%        157%          134%               60%         58%                 13%                  --
100
                       Rate substr_assign substr_loop substr_return_ref unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       11818/s            --        -25%              -25%          -26%              -53%        -55%                -63%                -70%
substr_loop         15677/s           33%          --               -0%           -2%              -38%        -40%                -51%                -60%
substr_return_ref   15752/s           33%          0%                --           -2%              -37%        -40%                -51%                -60%
unpack_assign       16061/s           36%          2%                2%            --              -36%        -39%                -50%                -59%
unpack_return_ref   25121/s          113%         60%               59%           56%                --         -4%                -22%                -35%
unpack_loop         26188/s          122%         67%               66%           63%                4%          --                -19%                -33%
unpack_return_array 32310/s          173%        106%              105%          101%               29%         23%                  --                -17%
substr_return_array 38910/s          229%        148%              147%          142%               55%         49%                 20%                  --
1000
                      Rate substr_assign substr_return_ref substr_loop unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       1309/s            --              -23%        -25%          -28%              -52%        -54%                -62%                -67%
substr_return_ref   1709/s           31%                --         -3%           -6%              -38%        -41%                -51%                -57%
substr_loop         1756/s           34%                3%          --           -3%              -36%        -39%                -49%                -56%
unpack_assign       1815/s           39%                6%          3%            --              -34%        -37%                -48%                -55%
unpack_return_ref   2738/s          109%               60%         56%           51%                --         -5%                -21%                -32%
unpack_loop         2873/s          120%               68%         64%           58%                5%          --                -17%                -28%
unpack_return_array 3470/s          165%              103%         98%           91%               27%         21%                  --                -14%
substr_return_array 4015/s          207%              135%        129%          121%               47%         40%                 16%                  --
10000
                     Rate substr_assign substr_return_ref substr_loop unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       131/s            --              -23%        -27%          -28%              -52%        -55%                -63%                -67%
substr_return_ref   171/s           30%                --         -5%           -6%              -38%        -42%                -52%                -57%
substr_loop         179/s           37%                5%          --           -1%              -35%        -39%                -50%                -55%
unpack_assign       181/s           38%                6%          1%            --              -34%        -38%                -49%                -55%
unpack_return_ref   274/s          109%               60%         53%           51%                --         -6%                -23%                -32%
unpack_loop         293/s          123%               71%         63%           62%                7%          --                -18%                -27%
unpack_return_array 356/s          171%              108%         98%           96%               30%         21%                  --                -11%
substr_return_array 400/s          205%              134%        123%          121%               46%         37%                 13%                  --
100000
                      Rate substr_assign substr_return_ref substr_loop unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       13.0/s            --              -22%        -26%          -29%              -51%        -55%                -63%                -67%
substr_return_ref   16.7/s           29%                --         -5%           -8%              -37%        -43%                -52%                -58%
substr_loop         17.6/s           36%                5%          --           -3%              -33%        -40%                -50%                -56%
unpack_assign       18.2/s           40%                9%          3%            --              -31%        -37%                -48%                -54%
unpack_return_ref   26.4/s          103%               58%         50%           45%                --         -9%                -25%                -34%
unpack_loop         29.1/s          124%               74%         65%           60%               10%          --                -17%                -27%
unpack_return_array 35.1/s          170%              110%         99%           93%               33%         20%                  --                -12%
substr_return_array 39.7/s          206%              137%        125%          118%               50%         36%                 13%                  --

Итак, вернемся к исходному вопросу: "is unpack() быстрее, чем substr()?" Ответ: всегда, для этого типа приложения - если вы не заботитесь о возвращаемых значениях;)

Ответ 3

Тест не является недостатком, но он искажен. substr лучше, если все, что вам нужно сделать, - извлечь довольно простую подстроку из строки, но это о ней. Например, даже эту простую задачу выполнить нелегко с помощью substr:

$foo = '123foo456789bar89012';
my ($t1,$t2,$t3,$t4,$t5) = unpack("A3A3A6A3A5",$foo);

Вот где вы должны увидеть резкое различие между substr и unpack.

Ответ 4

Я получаю похожие результаты в вопросе под Ubuntu 9:

This is perl, v5.10.0 built for i486-linux-gnu-thread-multi
10
       Rate unpack substr
unpack 535925/s     --    -3%
substr 552749/s     3%     --
100
      Rate unpack substr
unpack 57957/s     --    -5%
substr 61264/s     6%     --
1000
     Rate unpack substr
unpack 4716/s     --   -22%
substr 6075/s    29%     --
10000
    Rate unpack substr
unpack 466/s     --   -24%
substr 609/s    31%     --
100000
     Rate unpack substr
unpack 46.3/s     --   -23%
substr 60.5/s    31%     --

Но я не уверен, что это актуально. Я не склонен использовать распаковку для простых выделений строк из-за своей нечестивой форматированной строки: -)

Я думаю, где бы он сиял, это извлечение закодированных целых чисел и всех видов другой двоичной информации, где я буду использовать его.

Одна вещь, которую вы должны взять от Матфея и моих (и ваших) тестов, заключается в том, что это будет зависеть от факторов окружающей среды. И имейте в виду, что скорость, в то время как хорошая, - это не все и все - я не думаю, что написал много кода, на который серьезно повлияет только возможность выполнять 4,6 миллиона экстракций в секунду, а не 6 миллионов:-) Возможно, вам понадобится эта дополнительная производительность, но я сомневаюсь в этом для большинства приложений, написанных на Perl.

Ответ 5

Не сказать, что я не доверяю вашим результатам, но какая система вы используете? Я запустил ваш script на Ubuntu 8.10 (perl 5.10) со следующими результатами:

 [email protected]:~$ perl -v

This is perl, v5.10.0 built for x86_64-linux-gnu-thread-multi
 [email protected]:~$ ./test.pl
10
           Rate substr unpack
substr 587390/s     --   -10%
unpack 650343/s    11%     --
100
          Rate substr unpack
substr 66060/s     --    -5%
unpack 69433/s     5%     --
1000
         Rate substr unpack
substr 6847/s     --    -2%
unpack 6977/s     2%     --
10000
        Rate substr unpack
substr 683/s     --    -1%
unpack 693/s     1%     --
100000
         Rate substr unpack
substr 68.3/s     --    -0%
unpack 68.4/s     0%     --

Мои результаты с моей локальной машины Windows (это то, что я предполагаю, что вы используете, судя по моим результатам):

>perl -v

This is perl, v5.10.0 built for MSWin32-x86-multi-thread

>perl test.pl
10
           Rate unpack substr
unpack 125210/s     --   -50%
substr 252878/s   102%     --
100
          Rate unpack substr
unpack 12677/s     --   -56%
substr 28854/s   128%     --
1000
         Rate unpack substr
unpack  963/s     --   -66%
substr 2846/s   196%     --
10000
         Rate unpack substr
unpack 78.8/s     --   -73%
substr  291/s   269%     --
100000
         Rate unpack substr
unpack 4.88/s     --   -82%
substr 27.2/s   457%     --

Если бы мне пришлось хорошо подумать о разнице, я бы подумал, что Windows не имеет собственной функции pack/unpack, поэтому Perl должен каким-то образом подражать ей. Мой 2c в любом случае.