Por que o str.translate é muito mais rápido no Python 3.5 comparado ao Python 3.4?



string python-3.x (1)

Eu estava tentando remover caracteres indesejados de uma determinada string usando text.translate() no Python 3.4.

O código mínimo é:

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

Funciona como esperado. No entanto, o mesmo programa, quando executado no Python 3.4 e no Python 3.5, oferece uma grande diferença.

O código para calcular os tempos é

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

O programa Python 3.4 leva 1.3ms, enquanto o mesmo programa no Python 3.5 leva apenas 26.4μs .

O que melhorou no Python 3.5 que o torna mais rápido comparado ao Python 3.4?


TL; DR - NÚMERO 21118

A longa história

Josh Rosenberg descobriu que a função str.translate() é muito lenta em comparação com o bytes.translate , ele levantou um problema , afirmando que:

No Python 3, str.translate() é geralmente uma pessimização de desempenho, não otimização.

Por que foi str.translate() lento?

A principal razão para o str.translate() ser muito lento era que a pesquisa costumava estar em um dicionário Python.

O uso de maketrans esse problema. A abordagem semelhante usando bytes cria uma matriz C de 256 itens para pesquisa rápida de tabela. Portanto, o uso de um Python dict nível mais alto torna o str.translate() no Python 3.4 muito lento.

O que aconteceu agora?

A primeira abordagem foi adicionar um pequeno patch, translate_writer , no entanto, o aumento de velocidade não foi tão agradável. Logo outro patch fast_translate foi testado e rendeu resultados muito bons de até 55% de aceleração.

A principal alteração, como pode ser vista no arquivo, é que a pesquisa do dicionário Python é alterada para uma pesquisa no nível C.

As velocidades agora são quase as mesmas que os bytes

                                unpatched           patched

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

Uma pequena nota aqui é que o aprimoramento de desempenho é apenas proeminente em seqüências de caracteres ASCII.

Como JFSebastian menciona em um comment abaixo, Antes de 3.5, traduzir usado para trabalhar da mesma maneira para ambos os casos ASCII e não-ASCII. No entanto, a partir do 3.5 ASCII, o caso é muito mais rápido.

Antes ASCII vs não-ascii costumava ser quase o mesmo, no entanto, agora podemos ver uma grande mudança no desempenho.

Pode ser uma melhoria de 71,6 μs para 2,33 μs, como visto nesta answer .

O código a seguir demonstra isso

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

Tabulação dos resultados:

         Python 3.4    Python 3.5  
Ascii     91.2          2.3 
Unicode   101           117




python-3.5