Является 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 в любом случае.