2012-10-23 19 views
30

mam na myśli to pytanie, a zwłaszcza komentarze do pierwszej odpowiedzi od @David Robinson i @mgilson: Sum the second value of each tuple in a listDlaczego rozpakowywanie jest szybsze niż dostęp do indeksu?

Oryginalny pytanie było podsumować drugą wartość każdego tuble:

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

pierwsza odpowiedź:

sum(n for _, n in structure) 

Druga odpowiedź:

sum(x[1] for x in structure) 

Zgodnie z dyskusją, pierwsza odpowiedź jest o 50% szybsza.

Kiedy już zorientowałem się, co robi pierwsza odpowiedź (pochodząca z Perla, szukałem w Google dla specjalnych _ zmiennych w pythonie), zastanawiałem się, jak to się stało, że pojawia się jako zadanie czystego podzbioru (otrzymując tylko drugi element każdego z nich tuple vs. pobieranie i wiązanie zmiennych obu elementów) jest rzeczywiście wolniejsze? Czy jest to brakująca możliwość optymalizacji dostępu do indeksu w Pythonie? Czy brakuje mi czegoś, co wymaga drugiej odpowiedzi, która wymaga czasu?

Odpowiedz

32

Jeśli przyjrzeć się z kodu bajtowego Pythona, staje się oczywiste, bardzo szybko, dlaczego rozpakowywanie jest szybsze:

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

Operacja rozpakowania krotki jest prostym kodem bajtowym (UNPACK_SEQUENCE), podczas gdy operacja indeksowania musi wywołać metodę na krotce (BINARY_SUBSCR). Operacja rozpakowania może odbywać się, inline, w pętli oceny python, podczas gdy wywołanie subskrypcji wymaga sprawdzenia funkcji obiektu krotki w celu pobrania wartości, przy użyciu PyObject_GetItem.

The UNPACK_SEQUENCE opcode source code specjalnego przypadkach krotka python lub lista rozpakować gdzie długość sekwencji odpowiada długości argumentów dokładnie:

 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 

Powyższy kod sięga do natywnej struktury krotki i pobiera wartości bezpośrednio; nie trzeba używać ciężkich połączeń, takich jak PyObject_GetItem, które muszą wziąć pod uwagę, że obiekt może być niestandardową klasą python.

Opcja BINARY_SUBSCR opcode jest zoptymalizowana tylko dla python o listach; wszystko, co nie jest rodzimą listą pytonów, wymaga wywołania PyObject_GetItem.

+1

Niemniej jednak, spodziewam się, że 'BINARY_SUBSCRIPT' będzie dość wydajną operacją (po wyszukiwaniu), podczas gdy' UNPACK_SEQUENCE' nie jest (O (1) vs. O (n), jak to było), a operacja indeksu dolnego wymaga o jeden operacja przechowywania. W rzeczywistości twoje formatowanie kodu jest mylące - obie operacje przyjmują taką samą liczbę kodów operacji i nie wyjaśniłeś właściwie, dlaczego rozpakowywanie jest szybsze niż indeksowanie: w szczególności, dlaczego rozpakowywanie nie wymaga również wyszukiwania metod za pomocą '' '' ' PyObject_GetItem'? –

+1

@KonradRudolph: Rozszerzony. Co ciekawe, jeśli zastąpisz krotkę listą, tabele mogą zostać odwrócone. –

16

Indeksowanie przechodzi przez specjalną metodę __getitem__, która w związku z tym musi wykonać wyszukiwanie i wykonanie funkcji dla każdego elementu. Oznacza to, że dla listy pozycji n kończy się wykonywanie wyszukiwań/wywołań n.

Rozpakowywanie nie musi się z tym obchodzić podczas pracy z rodzimymi listami/krotkami; to po prostu przechodzi przez __iter__ która jest jedną rozmowę, a następnie rozpakowuje Powstała sekwencja w C

+0

+1 nie myślałem o tym –

+0

Aby było jasne: czy mówisz, że rozpakowywanie nie odbywa się za pomocą specjalnej metody, a więc nie sprawdza metody? Więc rozpakowywanie działa tylko na rodzimych obiektach Pythona ('list',' krotce'), a następnie? –

+0

@KonradRudolph Nie - zanotuj wzmiankę o "__iter__". Mówię tylko, że musi to zrobić tylko raz *, a zatem taka operacja to O (1) w porównaniu do O (n) wywoływania '__getitem__' dla * każdej pozycji * w sekwencji. – Amber

Powiązane problemy