str rendimiento en python

88

Mientras perfilaba un fragmento de código Python ( python 2.6hasta 3.2), descubrí que el strmétodo para convertir un objeto (en mi caso, un número entero) en una cadena es casi un orden de magnitud más lento que usar el formato de cadena.

Aquí está el punto de referencia

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

¿Alguien sabe por qué es así? ¿Me estoy perdiendo de algo?

Luca Sbardella
fuente
2
Y qué tal'{}'.format(100000)
wim
Ese es el más lento pero también el más flexible.
Luca Sbardella

Respuestas:

106

'%s' % 100000 es evaluado por el compilador y es equivalente a una constante en tiempo de ejecución.

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

%con una expresión en tiempo de ejecución no es (significativamente) más rápido que str:

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

Tenga en cuenta que strtodavía es un poco más lento, como dijo @DietrichEpp, esto se debe a que strimplica operaciones de búsqueda y llamada de función, mientras que se %compila en un solo código de bytes inmediato:

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

Por supuesto, lo anterior es cierto para el sistema en el que probé (CPython 2.7); otras implementaciones pueden diferir.

georg
fuente
De hecho, esta parece ser la razón, lo intenté yo mismo y el formato de cadena es aproximadamente un 5% más rápido que str. Gracias por responder. No hay razón para cambiar el código en todas partes :-)
Luca Sbardella
2
Para desarrollar más: stres un nombre que se puede vincular a algo diferente al tipo de cadena, pero el formato de cadena, es decir, el str.__mod__método, no se puede reemplazar, lo que permite que el compilador realice la optimización. El compilador no hace mucho en cuanto a optimización, pero hace más de lo que podría pensar :)
Karl Knechtel
4
... y la lección que debe aprender aquí es: ¡nunca use literales en pruebas como estas!
UncleZeiv
Esta entrada de blog en particular puede interesarle: skymind.com/~ocrow/python_string . Contiene una tabla de puntos de referencia para varios métodos de concatenación de cadenas similar a lo que proporcionó anteriormente.
Aaron Newton
14

Una razón que me viene a la mente es el hecho de que str(100000)implica una búsqueda global, pero "%s"%100000no es así. Lo strglobal debe buscarse en el ámbito global. Esto no explica toda la diferencia:

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

Como señaló thg435 ,

>>> Timer('"%s"%100000',).timeit()
0.034214019775390625
>>> Timer('"%s"%x','x=100000').timeit()
0.2940788269042969
Dietrich Epp
fuente