Perché str.translate è molto più veloce in Python 3.5 rispetto a Python 3.4?


116

Stavo cercando di rimuovere i caratteri indesiderati da una determinata stringa utilizzando text.translate()in Python 3.4.

Il codice minimo è:

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

Funziona come previsto. Tuttavia lo stesso programma quando viene eseguito in Python 3.4 e Python 3.5 dà una grande differenza.

Il codice per calcolare i tempi è

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)"

Il programma Python 3.4 richiede 1.3ms mentre lo stesso programma in Python 3.5 richiede solo 26.4μs .

Cosa è migliorato in Python 3.5 che lo rende più veloce rispetto a Python 3.4?


11
Mentre stiamo parlando di prestazioni, non sarebbe meglio per generare il vostro mapper in questo modo: dict.fromkeys(ord(c) for c in '@#$')?
Thomas K

1
@ ThomasK Ho scoperto che questo ha fatto una differenza significativa. Sì, a modo tuo è migliore.
Bhargav Rao

Volevi dire 50 volte più veloce?
assylias

@assylias ho fatto 1300 - 26,4 e poi diviso per 1300. Ho ottenuto quasi il 95%, quindi ho scritto :) In realtà è più di 50 volte più veloce ... Ma il mio calcolo è sbagliato? Sono un po 'debole in matematica. Imparerò presto la matematica. :)
Bhargav Rao

3
dovresti farlo in modo inverso: 26/1300 = 2% quindi la versione più veloce impiega solo il 2% del tempo impiegato dalla versione più lenta => è 50 volte più veloce.
assylias

Risposte:


148

TL; DR - NUMERO 21118


La lunga storia

Josh Rosenberg ha scoperto che la str.translate()funzione è molto lenta rispetto a bytes.translate, ha sollevato un problema , affermando che:

In Python 3, di str.translate()solito è una pessimizzazione delle prestazioni, non un'ottimizzazione.

Perché era str.translate()lento?

La ragione principale per str.translate()essere molto lenti era che la ricerca era in un dizionario Python.

L'uso di maketransha peggiorato questo problema. L'approccio simile che utilizza bytescrea un array C di 256 elementi per una rapida ricerca nella tabella. Quindi l'uso di Python di livello superiore dictrende str.translate()molto lento in Python 3.4.

Cos'è successo ora?

Il primo approccio è stato quello di aggiungere una piccola patch, translate_writer , tuttavia l'aumento di velocità non è stato così piacevole. Presto è stata testata un'altra patch fast_translate che ha prodotto ottimi risultati con un aumento di velocità fino al 55%.

Il cambiamento principale, come si può vedere dal file, è che la ricerca nel dizionario Python viene modificata in una ricerca di livello C.

Le velocità ora sono quasi le stesse di bytes

                                unpatched           patched

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

Una piccola nota qui è che il miglioramento delle prestazioni è prominente solo nelle stringhe ASCII.

Come menzionato da JFSebastian in un commento sotto, prima della 3.5, la traduzione funzionava allo stesso modo sia per i casi ASCII che per quelli non ASCII. Tuttavia da 3.5 ASCII case è molto più veloce.

Precedenti ASCII vs non ASCII erano quasi gli stessi, tuttavia ora possiamo vedere un grande cambiamento nelle prestazioni.

Può essere un miglioramento da 71,6μs a 2,33μs come si vede in questa risposta .

Il codice seguente lo dimostra

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

Tabulazione dei risultati:

         Python 3.4    Python 3.5  
Ascii     91.2          2.3 
Unicode   101           117

13
Questo è uno dei commit: github.com/python/cpython/commit/…
filmor

nota: il case ascii rispetto a quello non ascii può differire in modo significativo nelle prestazioni. Non si tratta di 55%: come mostra la tua risposta, la velocità può essere 1000s% .
jfs

confrontare: 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). Quest'ultimo è molto (10 volte) più lento.
jfs

@ JF Oh, l'ho capito ora. Ho eseguito il tuo codice sia per 3.4 che per 3.5. Sto ottenendo Py3.4 più veloce per roba non ASCII. È una coincidenza? I risultati dpaste.com/15FKSDQ
Bhargav Rao

Prima della 3.5, sia i casi ascii che quelli non ascii sono probabilmente gli stessi per Unicode .translate(), cioè, il caso ascii è molto più veloce solo in Python 3.5 (non hai bisogno bytes.translate()di prestazioni lì).
jfs
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.