Pourquoi str.translate est-il beaucoup plus rapide dans Python 3.5 par rapport à Python 3.4?

116

J'essayais de supprimer les caractères indésirables d'une chaîne donnée en utilisant text.translate()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é en Python 3.4 et Python 3.5 donne une grande différence.

Le code pour calculer les horaires 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 en 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 par rapport à Python 3.4?

Bhargav Rao
la source
11
Alors que nous parlons de la performance, ne serait - il préférable de générer votre mappeur comme ceci: dict.fromkeys(ord(c) for c in '@#$')?
Thomas K
1
@ThomasK J'ai découvert que cela faisait une différence significative. Oui, votre chemin est meilleur.
Bhargav Rao
Vouliez-vous dire 50 fois plus vite?
assylias
@assylias J'ai fait 1300 - 26,4 puis divisé par 1300. J'ai obtenu près de 95%, alors j'ai écrit :) C'est en fait plus de 50 fois plus rapide ... Mais mon calcul est-il faux? Je suis un peu faible en maths. J'apprendrai bientôt les maths. :)
Bhargav Rao
3
vous devriez le faire dans le sens inverse: 26/1300 = 2% donc la version la plus rapide ne prend que 2% du temps pris par la version la plus lente => elle est 50x plus rapide.
assylias

Réponses:

148

TL; DR - NUMÉRO 21118


La longue histoire

Josh Rosenberg a découvert que la str.translate()fonction est très lente par rapport au bytes.translate, il a soulevé un problème , déclarant que:

En Python 3, il str.translate()s'agit généralement d'une pessimisation des performances, pas d'une optimisation.

Pourquoi était str.translate()lent?

La principale raison str.translate()d'être très lente était que la recherche se faisait auparavant dans un dictionnaire Python.

L'utilisation de maketransa aggravé ce problème. L'approche similaire utilisant bytescrée un tableau C de 256 éléments pour une recherche rapide dans la table. Par conséquent, l'utilisation de Python de niveau supérieur dictrend le str.translate()Python 3.4 très lent.

Que s'est-il passé maintenant?

La première approche consistait à ajouter un petit patch, translate_writer , mais l'augmentation de la vitesse n'était pas si agréable. Bientôt un autre patch fast_translate a été testé et il a donné de très bons résultats allant jusqu'à 55% d'accélération.

Le principal changement, comme on peut le voir dans le fichier, est que la recherche du dictionnaire Python est transformée en une recherche de 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 n'est importante que dans les chaînes ASCII.

Comme le mentionne JFSebastian dans un commentaire ci-dessous, Avant 3.5, translate fonctionnait de la même manière pour les cas ASCII et non ASCII. Cependant, à partir de 3,5, le cas ASCII est beaucoup plus rapide.

Auparavant, ASCII et non-ascii étaient presque les mêmes, mais nous pouvons maintenant voir un grand changement dans les performances.

Cela peut être une amélioration de 71,6 μs à 2,33 μs, comme le montre cette réponse .

Le code suivant illustre cela

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

Tabulation des résultats:

         Python 3.4    Python 3.5  
Ascii     91.2          2.3 
Unicode   101           117
Bhargav Rao
la source
13
C'est l'un des commits: github.com/python/cpython/commit/…
filmor
Remarque: les cas ascii et non-ascii peuvent différer considérablement en termes de performances. Il ne s'agit pas de 55%: comme le montre votre réponse, l'accélération peut être de 1000s% .
jfs
comparer: 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)"(non-ascii). Ce dernier est beaucoup (10x) plus lent.
jfs
@JF Oh, je l'ai compris maintenant. J'ai exécuté votre code pour les versions 3.4 et 3.5. J'obtiens Py3.4 plus rapidement pour les trucs non-ascii. Est-ce par hasard? Les résultats dpaste.com/15FKSDQ
Bhargav Rao
Avant 3.5, les cas ascii et non-ascii sont probablement les mêmes pour Unicode, .translate()c'est-à-dire que le cas ascii est beaucoup plus rapide en Python 3.5 uniquement (vous n'avez pas besoin bytes.translate()de performances là-bas).
jfs