xf3 xf1 xe1 tildes ignorar escribir eliminar acentos python performance python-internals python-3.6 f-string

xf1 - ¿Por qué las cadenas con formato literal son tan lentas en Python 3.6 alfa?(ahora fijado en 3.6 estable)



u ''/ xf3 (3)

He descargado una compilación alfa de Python 3.6 del repositorio de Python Github, y una de mis nuevas características favoritas es el formato de cadena literal. Puede ser utilizado como tal:

>>> x = 2 >>> f"x is {x}" "x is 2"

Esto parece hacer lo mismo que usar la función de format en una instancia de str . Sin embargo, una cosa que he notado es que este formato de cadena literal es en realidad muy lento en comparación con solo el format llamada. timeit es lo que dice timeit acerca de cada método:

>>> x = 2 >>> timeit.timeit(lambda: f"X is {x}") 0.8658502227130764 >>> timeit.timeit(lambda: "X is {}".format(x)) 0.5500578542015617

Si uso una cadena como argumento de timeit , mis resultados siguen mostrando el patrón:

>>> timeit.timeit(''x = 2; f"X is {x}"'') 0.5786435347381484 >>> timeit.timeit(''x = 2; "X is {}".format(x)'') 0.4145195760771685

Como puede ver, usar el format lleva casi la mitad del tiempo. Espero que el método literal sea más rápido porque hay menos sintaxis involucrada. ¿Qué sucede detrás de escena que hace que el método literal sea mucho más lento?


Antes de 3.6 beta 1, la cadena de formato f''x is {x}'' se compiló al equivalente de ''''.join([''x is '', x.__format__('''')]) . El código resultante fue ineficiente por varias razones:

  1. construyó una secuencia de fragmentos de cuerda ...
  2. ... y esta secuencia era una lista, no una tupla! (Es un poco más rápido construir tuplas que listas).
  3. empujó una cadena vacía en la pila
  4. Buscó el método de join en la cadena vacía.
  5. invocó __format__ incluso en objetos Unicode simples, para los cuales __format__('''') siempre devolvería self , u objetos enteros, para los cuales __format__('''') como el argumento devolvió str(self) .
  6. __format__ método de __format__ no es ranurado

Sin embargo, para una cadena más compleja y más larga, las cadenas con formato literal aún habrían sido más rápidas que la llamada ''...''.format(...) , porque para esta última la cadena se interpreta cada vez que se formatea la cadena. .

Esta pregunta fue el principal motivador para el problema 27078, que solicitó un nuevo código de operación de bytecode de Python para concatenar una cadena de fragmentos. Serhiy Storchaka implementó este nuevo código de operación y lo fusionó en CPython para que esté disponible en Python 3.6 desde la versión beta 1 (y, por lo tanto, en la final de Python 3.6.0).

Como resultado, las cadenas con formato literal serán mucho más rápidas que string.format . A menudo, también son mucho más rápidos que el formato antiguo en Python 3.6, si solo está interpolando objetos str o int :

>>> timeit.timeit("x = 2; ''X is {}''.format(x)") 0.32464265200542286 >>> timeit.timeit("x = 2; ''X is %s'' % x") 0.2260766440012958 >>> timeit.timeit("x = 2; f''X is {x}''") 0.14437875000294298

f''X is {x}'' ahora compila a

>>> dis.dis("f''X is {x}''") 1 0 LOAD_CONST 0 (''X is '') 2 LOAD_NAME 0 (x) 4 FORMAT_VALUE 0 6 BUILD_STRING 2 8 RETURN_VALUE

El nuevo BUILD_STRING , junto con una optimización en el código FORMAT_VALUE elimina completamente las primeras 5 de las 6 fuentes de ineficiencia. El método __format__ aún no tiene ranuras, por lo que requiere una búsqueda de diccionario en la clase y, por lo tanto, llamarla es necesariamente más lento que llamar a __str__ , pero ahora se puede evitar completamente una llamada en los casos comunes de formato de instancias int o str (no subclases !) sin especificadores de formato.


Solo una actualización que indica que esto parece resolverse en la versión Python3.6.

>>> import dis >>> dis.dis(compile(''f"X is {x}"'', '''', ''exec'')) 1 0 LOAD_CONST 0 (''X is '') 2 LOAD_NAME 0 (x) 4 FORMAT_VALUE 0 6 BUILD_STRING 2 8 POP_TOP 10 LOAD_CONST 1 (None) 12 RETURN_VALUE >>> dis.dis(compile(''"X is {}".format(x)'', '''', ''exec'')) 1 0 LOAD_CONST 0 (''X is {}'') 2 LOAD_ATTR 0 (format) 4 LOAD_NAME 1 (x) 6 CALL_FUNCTION 1 8 POP_TOP 10 LOAD_CONST 1 (None) 12 RETURN_VALUE


Nota : esta respuesta se escribió para las versiones alfa de Python 3.6. Un nuevo código de operación agregado a 3.6.0b1 mejoró significativamente el rendimiento de f-string.

La sintaxis f"..." se convierte efectivamente en una operación str.join() en las partes de la cadena literal alrededor de las expresiones {...} , y los resultados de las propias expresiones pasaron a través del método object.__format__() pasando cualquier :.. especificación de formato en). Puedes ver esto al desmontar:

>>> import dis >>> dis.dis(compile(''f"X is {x}"'', '''', ''exec'')) 1 0 LOAD_CONST 0 ('''') 3 LOAD_ATTR 0 (join) 6 LOAD_CONST 1 (''X is '') 9 LOAD_NAME 1 (x) 12 FORMAT_VALUE 0 15 BUILD_LIST 2 18 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 21 POP_TOP 22 LOAD_CONST 2 (None) 25 RETURN_VALUE >>> dis.dis(compile(''"X is {}".format(x)'', '''', ''exec'')) 1 0 LOAD_CONST 0 (''X is {}'') 3 LOAD_ATTR 0 (format) 6 LOAD_NAME 1 (x) 9 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 12 POP_TOP 13 LOAD_CONST 1 (None) 16 RETURN_VALUE

Tenga en cuenta los BUILD_LIST LOAD_ATTR .. (join) BUILD_LIST y LOAD_ATTR .. (join) en ese resultado. El nuevo FORMAT_VALUE toma la parte superior de la pila más un valor de formato (analizado en tiempo de compilación) para combinarlos en una llamada de object.__format__() .

Entonces su ejemplo, f"X is {x}" , se traduce a:

''''.join(["X is ", x.__format__('''')])

Tenga en cuenta que esto requiere que Python cree un objeto de lista y llame al método str.join() .

La llamada str.format() también es una llamada de método, y después del análisis todavía hay una llamada al x.__format__('''') involucrada, pero fundamentalmente, no hay creación de lista involucrada aquí. Es esta diferencia la que hace que el método str.format() más rápido.

Tenga en cuenta que Python 3.6 solo se ha lanzado como una compilación alfa; Esta implementación todavía puede cambiar fácilmente. Consulte el PEP 494 - Programación de lanzamiento de Python 3.6 para ver la tabla de tiempo, así como el problema de Python # 27078 (abierto en respuesta a esta pregunta) para una discusión sobre cómo mejorar aún más el rendimiento de los literales de cadena con formato.