Frage an perl – Ist Perls unpack () jemals schneller als substr ()?

14

Ich habe das mehrmals gelesenunpack() ist schneller alssubstr()Dies gilt insbesondere, wenn die Anzahl der Teilzeichenfolgen zunimmt. Diese Benchmark schlägt jedoch etwas anderes vor. Ist mein Benchmark fehlerhaft oder liegt der angebliche Leistungsvorteil vonunpack() ein Überbleibsel von älteren Perl-Versionen?

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

Ausgabe (unter 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%     --

Wie in einigen Antworten darauf hingewiesen,unpack() funktioniert schlecht unter Windows. Hier ist die Ausgabe auf einer Solaris-Maschine - bei weitem nicht so entscheidend, abersubstr() gewinnt noch das Fußrennen:

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%     --
@ Brian Ich nehme Ihren Punkt, aber der Kommentar scheint nicht zielgerichtet. Hier ist ein näheres Analogon: "Ist ein Gepardje schneller als eine Gans? Hier ist einSpezifisch Beispiel von ihnen Rennen gegeneinander. War mein Test voreingenommen? "? Antwort:" Ja, voreingenommen. Die Gans war auf Steroiden. " FMc
Nichts ist schneller als alles. Sie müssen es auf ein bestimmtes Problem anwenden. Ist ein Gepard schneller als eine Gans? Vielleicht über 100 Meter, aber nicht über einen Ozean. :) brian d foy

Deine Antwort

5   die antwort
3

Seitdem ich diese Frage gestellt habe, habe ich ein Benchmarking durchgeführtsubstr gegenunpack mehrmals unter verschiedenen Bedingungen. Hier sind einige Dinge, die ich gelernt habe:

Richten Sie den Benchmark nicht so ein, dass Perl-Funktionen im ungültigen Kontext aufgerufen werden (wie ich es in meiner ursprünglichen Frage getan habe; siehe die hilfreiche Antwort von dlowe). Einige Perl-Funktionen weisen Optimierungen auf, wenn sie im ungültigen Kontext aufgerufen werden (und diese Optimierungen scheinen je nach Betriebssystem zu variieren), was möglicherweise zu einer Verschiebung Ihrer Benchmarking-Ergebnisse führt.

Wenn Ihr Gebrauch vonsubstr beinhaltet eine Schleife (zum Beispiel das Durchlaufen einer Liste von Spaltenpositionen),unpack ist immer schneller. Die scheinbare Langsamkeit vonsubstr In dieser Situation ist aufgrund des Overheads der Schleife nichtsubstr selbst.

Wenn nur ein paar Felder erforderlich sind,substr ist in der Regel schneller oder so schnell wieunpack.

Wenn mehr als ein paar Felder erforderlich sind, werden diese direkt miteinander verglichenunpack und eine entsprechende Anzahl vonsubstr Anrufe variieren nicht sehr, wenn die Anzahl der Felder zunimmt: Beide Ansätze werden mit der gleichen Geschwindigkeit langsamer.

Die Ergebnisse können je nach Betriebssystem variieren. Auf meinem Windows XP-Computerunpack hatte eine leichte Kante, wenn mehr als ein paar Felder benötigt wurden. Auf unseren Solaris-Maschinen an meinem Arbeitsplatzsubstr war immer schneller, auch in hunderten von Feldern.

Endeffekt: die Leistung vonunpack gegensubstr ist kein sehr großes Problem, unabhängig von der Anzahl der Felder. Verwenden Sie einen beliebigen Ansatz, um den klarsten Code zu erhalten. Wenn Sie sich mit findensubstr in einem Schleifenkonstrukt wird jedoch auf umgeschaltetunpack führt zu einem bemerkenswerten Geschwindigkeitsschub.

4

Ich erhalte ähnliche Ergebnisse wie der Fragesteller unter 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%     --

Ich bin mir aber nicht sicher, ob dies relevant ist. Ich neige nicht dazu, das Entpacken für einfache Zeichenfolgenextraktionen zu verwenden, da es eine unheilige Formatzeichenfolge ist :-)

Ich denke, wo es scheinen würde, würde ich codierte ganze Zahlen und alle möglichen anderen binären Informationen extrahieren, wo ich sie verwenden würde.

Eine Sache, die Sie Matthews und meinen (und Ihren) Benchmarks entnehmen sollten, ist, dass sie stark von Umweltfaktoren abhängen. Und bedenken Sie, dass Geschwindigkeit zwar gut ist, aber nicht das A und O - ich glaube, ich habe nicht viel Code geschrieben, der ernsthaft davon betroffen wäre, wenn nur 4,6 Millionen Extraktionen pro Sekunde ausgeführt werden könnten, anstatt 6 Millionen :-) Dukann brauche diese zusätzliche Leistung, aber ich bezweifle es für die meisten in Perl geschriebenen Anwendungen.

+ 1 im Allgemeinen, aber besonders beim Entpacken von Formatstrings ... Ich stimme Ihnen besonders zu, wenn es darum geht, dass das Packen / Entpacken für binäre Daten ausgelegt ist, anstatt nur Teilstrings zu verwenden. Matthew Scharley
2

Um nicht zu sagen, dass ich Ihren Ergebnissen misstraue, aber auf welchem ​​System läuft das? Ich habe Ihr Skript unter Ubuntu 8.10 (Perl 5.10) mit folgenden Ergebnissen ausgeführt:

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

Meine Ergebnisse von meinem lokalen Windows-Computer (was ich denke, dass Sie verwenden, gemessen an meinen Ergebnissen):

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

Wenn ich den Unterschied richtig einschätzen müsste, würde ich sagen, dass Windows keine native Pack / Unpack-Funktion hat und Perl sie daher irgendwie emulieren muss. Mein 2c sowieso.

Ich erhalte ähnliche Ergebnisse auf meinem Ubuntu 8.10. Brad Gilbert
Die Ergebnisse auf einem MacBook (OSX) ähneln denen auf Ubuntu 8.10 ... Massa
Hervorragender Punkt. Ich habe etwas mehr Output hinzugefügt. FMc
Ja ... Ich bin bereit zu wetten, dass die schlechte Leistung unter Windows auf eine schlechte (oder fehlende) native Implementierung von Pack / Unpack zurückzuführen ist ... Matthew Scharley
6

Der Test ist nicht fehlerhaft, aber verzerrt. substr ist besser, wenn Sie nur eine ziemlich einfache Teilzeichenfolge aus einer Zeichenfolge extrahieren müssen, aber das ist es auch. Zum Beispiel ist selbst diese einfache Aufgabe mit substr nicht einfach zu erledigen:

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

Dort sollten Sie einen dramatischen Unterschied zwischen substr und unpack feststellen.

19

In der Tat, Ihr Maßstabist auf eine wirklich, wirklich interessante Art und Weise fehlerhaft, aber es läuft darauf hinaus, dass Sie wirklich die relative Effizienz vergleichen, mit der Entpacken und Karte verglichen werden könnenwirf eine Liste weg, weil Benchmark :: cmpthese () die Funktionen im ungültigen Kontext ausführt.

Der Grund, warum Ihr Substrat die Nase vorn hat, ist die folgende Codezeile in pp_ctl.c pp_mapwhile ():

if (items && gimme != G_VOID) {

d.h. Perls Karte überspringt auf magische Weise eine Menge Arbeit (nämlich das Zuweisen von Speicherplatz für die Ergebnisse der Karte), wenn sie weiß, dass sie im ungültigen Kontext aufgerufen wird!

(Meine Vermutung an den Fenstern im Vergleich zu anderen, die ich oben gesehen habe, ist, dass die Zuweisung von Windows-basiertem Perl-Speicher schrecklich ist. Das Überspringen der Zuweisung ist also eine größere Ersparnis. Nur eine Vermutung, ich habe keine Windows-Box zum Spielen Die eigentliche Entpackungsimplementierung ist jedoch direkter C-Code und sollte sich von Windows zu Windows nicht wesentlich unterscheiden.)

Ich habe drei verschiedene Lösungen, um dieses Problem zu umgehen und einen faireren Vergleich zu erstellen:

Ordnen Sie die Liste einem Array zuDurchlaufen Sie die Liste in der Funktion und geben Sie nichts zurückEinen Verweis auf die Liste zurückgeben (den leeren Kontext verbergen)

Hier ist meine Version der% -Methoden mit allen drei Versionen:

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

Und meine Ergebnisse:

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

Zurück zur ursprünglichen Frage: "Ist unpack () jemals schneller als substr ()?" Antwort: immer, für diese Art von Anwendung - es sei denn, Sie interessieren sich nicht für die Rückgabewerte;)

Tolle Antwort - danke! FMc
Es hat Spaß gemacht! & Bitte :) dlowe
Dies ist fast dieselbe Situation, die ich im Kapitel Benchmarking in Mastering Perl erörtere. Im leeren Kontext ist grep wirklich schnell! brian d foy

Verwandte Fragen