2009-07-05 9 views
14

Kilka razy czytałem że unpack() jest szybszy niż substr(), zwłaszcza jak liczba podciągi wzrasta. Ten punkt odniesienia sugeruje jednak, że jest inaczej. Czy mój benchmark jest wadliwy, czy też rzekoma przewaga wydajności wynosi unpack() zatrzymania ze starszych wersji Perla?Czy rozpakowanie Perla() jest zawsze szybsze niż substr()?

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; 
} 

wyjściowa (w systemie 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%  -- 

Jak wskazano w niektórych odpowiedziach, unpack() robi źle na Windows. Poniżej znajduje się wyjście na maszynie solaris - nie aż tak decydujący, ale substr() nadal wygrywa wyścig Stopki:

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%  -- 
+2

Nic nie jest szybsze niż cokolwiek innego. Musisz zastosować go do konkretnego problemu. Czy gepard jest szybszy od gęsi? Może ponad 100 metrów, ale nie przez ocean. :) –

+3

@brian Rozumiem, ale komentarz wydaje się niecelny. Oto bliższy analog: "Czy gepard * kiedykolwiek * jest szybszy od gęsi? Oto przykład * ich wyścigi. Czy mój test był tendencyjny?"? Odpowiedź: "Tak, nieobiektywna, gęś była na sterydach". – FMc

Odpowiedz

3

Od zadawania tego pytania, porównałem substr z unpack jeszcze kilka razy, pod różnymi warunkami. Oto kilka rzeczy nauczyłem:

  • nie skonfigurujesz benchmark w sposób który wywołuje funkcje Perl w kontekście void (jak ja w moim oryginalne pytanie, zobacz pomocne odpowiedzi od dlowe). Niektóre funkcje Perla mają optymalizacje , gdy są wywoływane w pustym kontekście (i te optymalizacje mogą się różnić w zależności od systemu operacyjnego), potencjalnie pochylają wyniki testów porównawczych.

  • Jeśli korzystanie z substr polega pętli (np iteracji nad listę lokalizacji kolumn), unpack jest zawsze szybsza. Jednak widoczne opóźnienie wynoszące w tej sytuacji wynika z nakładu samego w sobie, a nie z samego siebie.

  • Jeśli wymagane są zaledwie kilka pól, substr jest na ogół szybciej lub jako szybko jak unpack.

  • Jeśli więcej niż kilka pola są wymagane, head-to-head porównania między unpack i równoważnej liczby substr połączeń nie zmieniają samo jak wielu dziedzinach wzrosty: oba podejścia stać wolniej w tym samym tempie.

  • Wyniki mogą się różnić w zależności od systemu operacyjnego. Na moim komputerze z systemem Windows XP, unpack miał niewielką przewagę, gdy było więcej niż potrzebnych kilka pól. Na naszych maszynach Solaris w moim miejscu pracy, substr zawsze było szybsze, nawet do setek pól.

Konkluzja: wydajność unpack vs. substr nie jest bardzo duży problem, niezależnie od liczby pól. Użyj któregokolwiek podejścia daje najczystszy kod. Jeśli jednak używasz substr w konstrukcji pętlowej, przełączenie na unpack spowoduje zauważalne zwiększenie prędkości.

2

nie znaczy, ja nie ufając swoje wyniki, ale jaki rodzaj systemu ty to działa na? Pobiegłem skrypt na Ubuntu 8.10 (perl 5.10) z następującymi wynikami:

[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%  -- 

moje wyniki z moim lokalnym komputerze z systemem Windows (czego zgaduję używasz, sądząc po wynikach):

>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%  -- 

Gdybym miał podjąć dobrą przypuszczenie na różnicę, zabrałbym dziką przypuszczenie i powiedzieć, że Windows nie posiada natywną funkcję pakietu/rozpakować i tak Perl ma naśladować go jakoś. Mój 2c i tak.

+0

Doskonały punkt. Dodałem trochę więcej wyników. – FMc

+0

wyniki na MacBooku (OSX) są podobne do twoich wyników Ubuntu 8.10 ... – Massa

+0

Mam podobne wyniki na moim Ubuntu 8.10. –

4

uzyskać podobne wyniki do pytającego pod 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%  -- 

Ale nie jestem pewien, że to jest istotne. Nie mają tendencję do używania Rozpakuj do prostych ekstrakcji smyczkowych, ze względu na jego bezbożnej formacie ciąg :-)

myślę, gdzie by to świecą się ekstrakcji zakodowanych liczb całkowitych i wszelkiego rodzaju innych informacji binarnej czyli tam, gdzie będę go używać .

Jedną z rzeczy, które powinieneś wziąć z Matthew's i moich (i twoich) testów porównawczych, jest to, że będzie to bardzo zależało od czynników środowiskowych. I pamiętaj, że szybkość, choć dobra, nie jest najważniejsza i końcowa - nie sądzę, żebym napisała dużo kodu, na który poważnie wpłynęłaby tylko możliwość wykonania 4.6 milionów ekstraktów na sekundę zamiast 6 milionów :-) Ty może potrzebujesz dodatkowej wydajności, ale wątpię w to dla większości aplikacji napisanych w Perlu.

+0

+1 ogólnie, ale przede wszystkim o rozpakowywaniu ciągu formatów ... W szczególności zgadzam się z tobą, że pomysł pakowania/rozpakowywania jest przeznaczony dla danych binarnych, a nie po prostu z podciągami. –

6

Test nie jest wadliwy, ale jest przekrzywiony. substr jest lepszy, jeśli wszystko, co musisz zrobić, to wyodrębnić dość prosty podciąg z łańcucha, ale to wszystko. Na przykład, nawet to proste zadanie, nie jest łatwo zrobić za pomocą substr:

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

Ów, gdzie powinieneś zobaczyć dramatyczną różnicę między substr i rozpakować.

19

W rzeczywistości, Twój odniesienia jest wadliwa, w naprawdę ciekawy sposób, ale to, co sprowadza się do tego, że to, czego naprawdę porównując to względna skuteczność, z jaką rozpakować vs. mapa może wyrzuć listę, ponieważ Benchmark :: cmpthese() wykonuje funkcje w pustym kontekście.

Powodem, dla którego substr jest wyświetlany na wierzchu jest ten wiersz kodu w pp_ctl.c pp_mapwhile():

if (items && gimme != G_VOID) { 

tj mapa Perl magicznie pomija kilka prac (przechowywanie mianowicie przeznaczając na wynikach MAP), jeśli wie, że jest wywoływana w kontekście nieważne!

(Moje przeczucie w oknach w porównaniu z innymi widzianymi powyżej to to, że przydzielanie pamięci perlowej opartej na oknach jest okropne, więc pomijanie przydziału jest tam większymi oszczędnościami - tylko przeczucie, ale nie mam okien Box, w którym można zagrać, ale rzeczywista rozpakowanie jest prostym kodem C i nie powinno zasadniczo różnić się w zależności od okna.)

Mam trzy różne rozwiązania do obejścia tego problemu i uzyskania bardziej rzetelnego porównania:

  1. przypisać listę tablicy
  2. pętlę listy wewnątrz funkcji i nic powrócić
  3. zwrócić odwołanie do listy (ukrywanie kontekst void)

Oto moja wersja% metodami, z wszystkich trzech wersjach:

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) }, 
); 

i moich wyników :

$ 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%     -- 

Wracając do pierwotnego pytania: "to rozpakować() coraz szybciej niż substr()?" Odpowiedź: zawsze, dla tego typu aplikacji - chyba że nie przejmujesz się wartościami zwracanymi;)

+1

Świetna odpowiedź - dziękuję! – FMc

+2

To było zabawne! i nie ma za co :) – dlowe

+1

To jest prawie ta sama sytuacja, którą omawiam w rozdziale Benchmarking w Mastering Perl. W pustym kontekście grep jest naprawdę szybki! –

Powiązane problemy