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

4 minutos de lectura

avatar de usuario
Bhargav Rao

Estaba tratando de eliminar caracteres no deseados de una cadena dada usando text.translate() en Pitón 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 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,3 ms 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?

  • Mientras hablamos de rendimiento, ¿no sería mejor generar su mapeador de esta manera: dict.fromkeys(ord(c) for c in '@#$')?

    – Tomas K.

    15 de diciembre de 2015 a las 11:49

  • @ThomasK Descubrí que esto marcó una diferencia significativa. Sí, tu manera es mejor.

    – Bhargav Rao

    15 de diciembre de 2015 a las 12:06

  • ¿Quiso decir 50 veces más rápido?

    – asilias

    18 de diciembre de 2015 a las 9:02

  • @assylias Hice 1300 – 26.4 y luego dividí por 1300. Obtuve casi el 95%, así que escribí 🙂 En realidad, es más de 50 veces más rápido… Pero, ¿está mal mi cálculo? Soy un poco débil en matemáticas. Pronto aprenderé matemáticas. 🙂

    – Bhargav Rao

    18 de diciembre de 2015 a las 9:17

  • deberías 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.

    – asilias

    18 de diciembre de 2015 a las 9:20

avatar de usuario
Bhargav Rao

TL; DR – NÚMERO 21118


la larga historia

Josh Rosenberg descubrió que el str.translate() La función es muy lenta en comparación con la bytes.translatelevantó un temadeclarando que:

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

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

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

el uso de maketrans hizo que este problema empeorara. El enfoque similar usando bytes crea una matriz C de 256 elementos para una búsqueda rápida en la tabla. De ahí el uso de Python de nivel superior dict hacer el str.translate() en Python 3.4 muy lento.

¿Que ha pasado ahora?

El primer enfoque fue agregar un pequeño parche, traductor_escritor, Sin embargo, el aumento de velocidad no fue tan agradable. Pronto otro parche traducción_rápida fue probado y arrojó resultados muy buenos de hasta un 55% de aceleración.

El cambio principal, como se puede ver en el archivo, es que la búsqueda en el 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 JFSebastian menciona en un comentario a continuación, antes de 3.5, la traducción solía funcionar de la misma manera para los casos ASCII y 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

  • Este es uno de los commits: github.com/python/cpython/commit/…

    – filmor

    15 de diciembre de 2015 a las 11:40

  • 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

    15 de diciembre de 2015 a las 19:51


  • comparar: python3.5 -m timeit -s "text = 'mJssissippi'*100; d=dict(J='i')" "text.translate(d)" (ascii) contra python3.5 -m timeit -s "text = 'm\U0001F602ssissippi'*100; d={'\U0001F602': 'i'}" "text.translate(d)" (no ascii). Este último es mucho (10x) más lento.

    – jfs

    15 de diciembre de 2015 a las 20:07

  • @JF Oh, lo entendí ahora. Ejecuté su código para 3.4 y 3.5. Estoy obteniendo Py3.4 más rápido para cosas que no son ascii. ¿Es por coincidencia? Los resultados dpaste.com/15FKSDQ

    – Bhargav Rao

    15 de diciembre de 2015 a las 20:14

  • 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() para el desempeño allí).

    – jfs

    15 dic 2015 a las 20:20

¿Ha sido útil esta solución?