¿Por qué str.translate es mucho más rápido en Python 3.5 en comparación con Python 3.4?

116

Estaba tratando de eliminar los caracteres no deseados de una cadena dada usando text.translate()Python 3.4.

El código mínimo es:

import sys 
s = 'abcde12345@#@$#%$'
mapper = dict.fromkeys(i for i in range(sys.maxunicode) if chr(i) in '@#$')
print(s.translate(mapper))

Funciona como se esperaba. Sin embargo, el mismo programa cuando se ejecuta en Python 3.4 y Python 3.5 da una gran diferencia.

El código para calcular los tiempos es

python3 -m timeit -s "import sys;s = 'abcde12345@#@$#%$'*1000 ; mapper = dict.fromkeys(i for i in range(sys.maxunicode) if chr(i) in '@#$'); "   "s.translate(mapper)"

El programa Python 3.4 toma 1.3ms mientras que el mismo programa en Python 3.5 toma solo 26.4μs .

¿Qué ha mejorado en Python 3.5 que lo hace más rápido en comparación con Python 3.4?

Bhargav Rao
fuente
11
Mientras hablamos de rendimiento, ¿no sería mejor generar su mapeador de esta manera dict.fromkeys(ord(c) for c in '@#$'):?
Thomas K
1
@ThomasK Descubrí que esto marcó una diferencia significativa. Sí, tu camino es mejor.
Bhargav Rao
¿Quiso decir 50 veces más rápido?
Assylias
@assylias Hice 1300 - 26,4 y luego dividí entre 1300. Obtuve casi el 95%, así que escribí :) En realidad es más de 50 veces más rápido ... ¿Pero mi cálculo es incorrecto? Soy un poco débil en matemáticas. Pronto aprenderé matemáticas. :)
Bhargav Rao
3
debe hacerlo al revés: 26/1300 = 2% por lo que la versión más rápida toma solo el 2% del tiempo que toma la versión más lenta => es 50 veces más rápida.
Assylias

Respuestas:

148

TL; DR - NÚMERO 21118


La larga historia

Josh Rosenberg descubrió que la str.translate()función es muy lenta en comparación con el bytes.translate, planteó un problema , afirmando que:

En Python 3, str.translate()suele ser una pesimización del rendimiento, no una optimización.

¿Por qué fue str.translate()lento?

La principal razón para str.translate()ser muy lento era que la búsqueda solía realizarse en un diccionario de Python.

El uso de maketransempeoró este problema. El enfoque similar que utiliza bytescrea una matriz C de 256 elementos para una búsqueda rápida en la tabla. Por lo tanto, el uso de Python de nivel superior dicthace que str.translate()en Python 3.4 sea muy lento.

¿Que ha pasado ahora?

El primer enfoque fue agregar un pequeño parche, translate_writer . Sin embargo, el aumento de velocidad no fue tan agradable. Pronto se probó otro parche fast_translate y arrojó muy buenos resultados de hasta un 55% de aceleración.

El cambio principal, como se puede ver en el archivo, es que la búsqueda del diccionario de Python se cambia a una búsqueda de nivel C.

Las velocidades ahora son casi las mismas que bytes

                                unpatched           patched

str.translate                   4.55125927699919    0.7898181750006188
str.translate from bytes trans  1.8910855210015143  0.779950579000797

Una pequeña nota aquí es que la mejora del rendimiento solo es prominente en las cadenas ASCII.

Como menciona JFSebastian en un comentario a continuación, antes de 3.5, la traducción solía funcionar de la misma manera tanto para casos ASCII como no ASCII. Sin embargo, a partir de 3.5 ASCII el caso es mucho más rápido.

Anteriormente, ASCII frente a no ascii solía ser casi lo mismo, sin embargo, ahora podemos ver un gran cambio en el rendimiento.

Puede ser una mejora de 71,6 μs a 2,33 μs como se ve en esta respuesta .

El siguiente código demuestra esto

python3.5 -m timeit -s "text = 'mJssissippi'*100; d=dict(J='i')" "text.translate(d)"
100000 loops, best of 3: 2.3 usec per loop
python3.5 -m timeit -s "text = 'm\U0001F602ssissippi'*100; d={'\U0001F602': 'i'}" "text.translate(d)"
10000 loops, best of 3: 117 usec per loop

python3 -m timeit -s "text = 'm\U0001F602ssissippi'*100; d={'\U0001F602': 'i'}" "text.translate(d)"
10000 loops, best of 3: 91.2 usec per loop
python3 -m timeit -s "text = 'mJssissippi'*100; d=dict(J='i')" "text.translate(d)"
10000 loops, best of 3: 101 usec per loop

Tabulación de los resultados:

         Python 3.4    Python 3.5  
Ascii     91.2          2.3 
Unicode   101           117
Bhargav Rao
fuente
13
Esta es una de las confirmaciones: github.com/python/cpython/commit/…
filmor
nota: el caso ascii vs. no ascii puede diferir significativamente en el rendimiento. No se trata de 55%: como muestra su respuesta, la aceleración puede ser 1000s% .
jfs
comparar: python3.5 -m timeit -s "text = 'mJssissippi'*100; d=dict(J='i')" "text.translate(d)"(ascii) vs. python3.5 -m timeit -s "text = 'm\U0001F602ssissippi'*100; d={'\U0001F602': 'i'}" "text.translate(d)"(no ascii). Este último es mucho (10 veces) más lento.
jfs
@JF Oh, ahora lo entendí. Ejecuté su código tanto para 3.4 como para 3.5. Obtengo Py3.4 más rápido para cosas que no son ascii. ¿Es por casualidad? Los resultados dpaste.com/15FKSDQ
Bhargav Rao
Antes de 3.5, tanto los casos ascii como los no ascii son probablemente los mismos para Unicode .translate(), es decir, el caso ascii es mucho más rápido solo en Python 3.5 (no necesita bytes.translate()rendimiento allí).
jfs