J'essayais de supprimer les caractères indésirables d'une chaîne donnée en utilisant text.translate()
in Python 3.4.
Le code minimal est:
import sys
s = 'abcde12345@#@$#%$'
mapper = dict.fromkeys(i for i in range(sys.maxunicode) if chr(i) in '@#$')
print(s.translate(mapper))
Cela fonctionne comme prévu. Cependant, le même programme lorsqu'il est exécuté dans Python 3.4 et Python 3.5 donne une grande différence.
Le code pour calculer les timings est
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)"
Le programme Python 3.4 prend 1,3 ms alors que le même programme est Python 3.5 ne prend que 26.4μs .
Qu'est-ce qui s'est amélioré dans Python 3.5 qui le rend plus rapide que Python 3.4?
TL; DR - NUMÉRO 21118
La longue histoire
Josh Rosenberg a découvert que la fonction str.translate()
est très lente par rapport au bytes.translate
. Il a soulevé un issue , déclarant que:
Dans Python 3,
str.translate()
est généralement une pessimisation des performances, pas une optimisation.
str.translate()
était-il lent?La raison principale pour que str.translate()
soit très lente était que la recherche était auparavant dans un dictionnaire Python.
L'utilisation de maketrans
a aggravé le problème. L'approche similaire utilisant bytes
construit un tableau C de 256 éléments pour une recherche rapide dans les tables. D'où l'utilisation du niveau supérieur Python dict
rend la str.translate()
dans Python 3.4 très lente.
La première approche consistait à ajouter un petit patch, translate_writer , Cependant, l’augmentation de la vitesse n’était pas aussi agréable. Bientôt un autre patch fast_translate a été testé et il a donné de très bons résultats, avec une accélération jusqu’à 55%.
Le principal changement constaté dans le fichier est que la recherche dans le dictionnaire Python est modifiée en une recherche au niveau C.
Les vitesses sont maintenant presque les mêmes que bytes
unpatched patched
str.translate 4.55125927699919 0.7898181750006188
str.translate from bytes trans 1.8910855210015143 0.779950579000797
Une petite remarque ici est que l'amélioration des performances est uniquement visible dans les chaînes ASCII.
Comme J.F.Sebastian le mentionne dans un commentaire ci-dessous, avant 3.5, la traduction fonctionnait de la même manière pour les cas ASCII et non ASCII. Cependant à partir de 3.5 ASCII la casse est beaucoup plus rapide.
Auparavant, ASCII vs non-ASCII était presque identique, mais nous pouvons maintenant constater un grand changement dans les performances.
Cela peut être une amélioration de 71,6 µs à 2,33 µs, comme indiqué dans ce réponse .
Le code suivant montre ceci
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
Tableau des résultats:
Python 3.4 Python 3.5
Ascii 91.2 2.3
Unicode 101 117