2012-10-23 31 views
30

Ich beziehe mich auf diese Frage, und vor allem die Kommentare zu der ersten Antwort von @ David Robinson und @mgilson: Sum the second value of each tuple in a listWarum ist das Entpacken schneller als der Zugriff per Index?

Die ursprüngliche Frage der zweite Wert jedes tuble zusammenzufassen war:

structure = [('a', 1), ('b', 3), ('c', 2)] 

Erste Antwort:

sum(n for _, n in structure) 

Zweite Antwort:

sum(x[1] for x in structure) 

Laut Diskussion ist die erste Antwort 50% schneller.

Sobald ich herausgefunden habe, was die erste Antwort (von Perl kommt, googelte ich für die spezielle _ Variable in Python), habe ich mich gefragt, was kommt als eine reine Teilmenge Aufgabe (nur das zweite Element von jedem bekommen Tupel vs. Getting und Bindung an Variablen beider Elemente) ist eigentlich langsamer? Ist es eine fehlende Möglichkeit, den Indexzugriff in Python zu optimieren? Fehle ich etwas, was die zweite Antwort braucht, was Zeit braucht?

Antwort

32

Wenn Sie einen Blick auf die Python-Bytecode nehmen, wird es ganz offensichtlich sehr schnell auspacken, warum ist schneller:

>>> import dis 
>>> def unpack_or_index(t=(0, 1)): 
...  _, x = t 
...  x = t[1] 
... 
>>> dis.dis(unpack_or_index) 
    2   0 LOAD_FAST    0 (t) 
       3 UNPACK_SEQUENCE   2 
       6 STORE_FAST    1 (_) 
       9 STORE_FAST    2 (x) 

    3   12 LOAD_FAST    0 (t) 
      15 LOAD_CONST    1 (1) 
      18 BINARY_SUBSCR  
      19 STORE_FAST    2 (x) 
      22 LOAD_CONST    0 (None) 
      25 RETURN_VALUE   

Der Tupel-Entpackungsvorgang ist ein einfacher Bytecode (UNPACK_SEQUENCE), während der Indizierungsvorgang eine Methode für das Tupel aufrufen muss (BINARY_SUBSCR). Die Entpack-Operation kann inline in der Python-Auswertungsschleife stattfinden, während der Subskriptionsaufruf das Aufrufen der Funktion für das Tupel-Objekt erfordert, um den Wert unter Verwendung von PyObject_GetItem abzurufen.

Die UNPACK_SEQUENCE opcode source code Sonderfälle eine Python-Tupel oder Liste auspacken, wo die die Sequenzlänge genau das Argument Länge entspricht:

 if (PyTuple_CheckExact(v) && 
      PyTuple_GET_SIZE(v) == oparg) { 
      PyObject **items = \ 
       ((PyTupleObject *)v)->ob_item; 
      while (oparg--) { 
       w = items[oparg]; 
       Py_INCREF(w); 
       PUSH(w); 
      } 
      Py_DECREF(v); 
      continue; 
     } // followed by an "else if" statement for a list with similar code 

Der obige Code der nativen Struktur des Tupels erreicht in und ruft die Werte direkt; Keine Notwendigkeit, schwere Aufrufe wie PyObject_GetItem zu verwenden, die berücksichtigen müssen, dass das Objekt eine benutzerdefinierte Python-Klasse sein könnte.

Die BINARY_SUBSCR opcode ist nur für Python optimiert Listen; alles, was keine native Python-Liste ist, erfordert einen Aufruf PyObject_GetItem.

+1

Nichtsdestotrotz würde ich erwarten, dass 'BINARY_SUBSCRIPT' eine recht effiziente Operation (nach dem Nachschlagen) ist, während 'UNPACK_SEQUENCE' nicht gleich (O (1) vs. O (n) ist) und die tiefgestellte Operation eine weniger benötigt Speichervorgang. Tatsächlich ist Ihre Formatierung des Codes irreführend - beide Operationen nehmen die gleiche Anzahl von Op-Codes an und Sie haben nicht wirklich erklärt, warum das Entpacken schneller ist als das Subskribieren: insbesondere warum benötigt das Entpacken nicht auch eine Methodensuche über ' PyObject_GetItem'? –

+1

@KonradRudolph: Erweitert. Interessanterweise, wenn Sie das Tupel durch eine Liste ersetzen, können die Tabellen gut umgesetzt werden. –

16

Indizierung geht durch die spezielle Methode __getitem__, die somit Funktion Lookup und Ausführung für jedes Element zu tun hat. Das bedeutet, dass Sie für eine Liste von n Artikeln n Lookups/Anrufe erledigen.

Entpacken muss nicht damit umgehen, wenn mit nativen Listen/Tupeln gearbeitet wird; es geht nur durch __iter__, die einen einzigen Anruf, und entpackt dann die resultierende Sequenz in C.

+0

+1 hat nicht daran gedacht –

+0

Um es klar zu stellen: Wollen Sie damit sagen, dass das Entpacken nicht über eine spezielle Methode erfolgt und daher keine Methodenabfrage durchführt? Also funktioniert das Entpacken nur mit nativen Python-Objekten ('list',' tuple')? –

+0

@KonradRudolph No - beachten Sie die Erwähnung von '__iter__'. Was ich sage, ist, dass es nur diesen Aufruf * einmal * tun muss, und daher ist eine solche Operation O (1) verglichen mit dem O (n) des Aufrufs von "__getitem__" für * jeden Gegenstand * in der Sequenz. – Amber

Verwandte Themen