str wydajność w Pythonie

88

Podczas profilowania fragmentu kodu Pythona ( python 2.6aż do 3.2) odkryłem, że strmetoda konwersji obiektu (w moim przypadku liczby całkowitej) na ciąg jest prawie o rząd wielkości wolniejsza niż przy użyciu formatowania ciągów.

Oto punkt odniesienia

>>> from timeit import Timer
>>> Timer('str(100000)').timeit()
0.3145311339386332
>>> Timer('"%s"%100000').timeit()
0.03803517023435887

Czy ktoś wie, dlaczego tak się dzieje? Czy coś mi brakuje?

Luca Sbardella
źródło
2
A co z'{}'.format(100000)
wim
To jest najwolniejsze, ale także najbardziej elastyczne.
Luca Sbardella

Odpowiedzi:

106

'%s' % 100000 jest oceniany przez kompilator i jest równoważny ze stałą w czasie wykonywania.

>>> import dis
>>> dis.dis(lambda: str(100000))
  8           0 LOAD_GLOBAL              0 (str)
              3 LOAD_CONST               1 (100000)
              6 CALL_FUNCTION            1
              9 RETURN_VALUE        
>>> dis.dis(lambda: '%s' % 100000)
  9           0 LOAD_CONST               3 ('100000')
              3 RETURN_VALUE        

%z wyrażeniem czasu wykonywania nie jest (znacząco) szybszy niż str:

>>> Timer('str(x)', 'x=100').timeit()
0.25641703605651855
>>> Timer('"%s" % x', 'x=100').timeit()
0.2169809341430664

Zwróć uwagę, że strjest to wciąż nieco wolniejsze, jak powiedział @DietrichEpp, ponieważ strobejmuje operacje wyszukiwania i wywołania funkcji, podczas gdy %kompiluje się do pojedynczego bezpośredniego kodu bajtowego:

>>> dis.dis(lambda x: str(x))
  9           0 LOAD_GLOBAL              0 (str)
              3 LOAD_FAST                0 (x)
              6 CALL_FUNCTION            1
              9 RETURN_VALUE        
>>> dis.dis(lambda x: '%s' % x)
 10           0 LOAD_CONST               1 ('%s')
              3 LOAD_FAST                0 (x)
              6 BINARY_MODULO       
              7 RETURN_VALUE        

Oczywiście powyższe odnosi się do systemu, na którym testowałem (CPython 2.7); inne implementacje mogą się różnić.

Georg
źródło
Rzeczywiście, wygląda to na powód, właśnie spróbowałem sam, a formatowanie ciągów jest tylko około 5% szybsze niż str. Dziękuję za odpowiedź. Nie ma powodu, żeby wszędzie zmieniać kod :-)
Luca Sbardella
2
Aby rozwinąć dalej: strto nazwa, która może być powiązana z czymś innym niż typ łańcucha, ale formatowania łańcucha - tj. str.__mod__Metody - nie można zastąpić, co pozwala kompilatorowi na wykonanie optymalizacji. Kompilator nie robi zbyt wiele w zakresie optymalizacji, ale robi więcej niż mogłoby się wydawać :)
Karl Knechtel
4
... a lekcja, której należy się nauczyć, brzmi: nigdy nie używaj literałów w takich testach!
UncleZeiv
Ten konkretny wpis na blogu może Cię zainteresować: skymind.com/~ocrow/python_string . Zawiera wykres wzorców dla różnych metod konkatenacji ciągów, podobnie do tego, co podałeś powyżej.
Aaron Newton
14

Jednym z powodów, które przychodzą na myśl, jest fakt, że str(100000)obejmuje wyszukiwanie globalne, ale "%s"%100000tak nie jest. strGlobalny musi być spojrzał w zasięgu globalnym. To nie wyjaśnia całej różnicy:

>>> Timer('str(100000)').timeit()
0.2941889762878418
>>> Timer('x(100000)', 'x=str').timeit()
0.24904918670654297

Jak zauważył thg435 ,

>>> Timer('"%s"%100000',).timeit()
0.034214019775390625
>>> Timer('"%s"%x','x=100000').timeit()
0.2940788269042969
Dietrich Epp
źródło