Por que str.translate é muito mais rápido no Python 3.5 em comparação com o Python 3.4?

116

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

O código mínimo é:

import sys 
s = 'abcde12345@#@$#%$'
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 em Python 3.4 e Python 3.5 oferece uma grande diferença.

O código para calcular os tempos é

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

O programa Python 3.4 leva 1,3 ms, enquanto o mesmo programa em Python 3.5 leva apenas 26,4 μs .

O que melhorou no Python 3.5 que o torna mais rápido em comparação com o Python 3.4?

Bhargav Rao
fonte
11
Enquanto estamos falando sobre desempenho, não seria melhor gerar seu mapeador assim dict.fromkeys(ord(c) for c in '@#$'):?
Thomas K
1
@ThomasK Eu descobri que isso fez uma diferença significativa. Sim, seu jeito é melhor.
Bhargav Rao
Você quis dizer 50x mais rápido?
assylias
@assylias Fiz 1300 - 26,4 e depois dividi por 1300. Consegui quase 95%, então escrevi :) Na verdade, é 50x mais rápido ... Mas meu cálculo está errado? Sou um pouco fraco em matemática. Vou aprender matemática em breve. :)
Bhargav Rao
3
você deve fazer da maneira inversa: 26/1300 = 2%, então a versão mais rápida leva apenas 2% do tempo gasto pela versão mais lenta => é 50x mais rápida.
Assylias

Respostas:

148

TL; DR - ISSUE 21118


A longa história

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

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

Por que foi str.translate()lento?

O principal motivo de str.translate()ser muito lento era que a pesquisa costumava estar em um dicionário Python.

O uso de maketranspiorou o problema. A abordagem semelhante usando bytescria uma matriz C de 256 itens para consulta rápida de tabela. Portanto, o uso de Python de nível superior dicttorna o str.translate()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 produziu resultados muito bons de até 55% de aumento de velocidade.

A principal mudança, conforme pode ser visto no arquivo, é que a pesquisa de dicionário Python foi alterada para uma pesquisa de nível C.

As velocidades agora são quase as mesmas que bytes

                                unpatched           patched

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

Uma pequena observação aqui é que o aprimoramento de desempenho só é proeminente em strings ASCII.

Como JFSebastian menciona em um comentário abaixo, Antes de 3.5, a tradução costumava funcionar da mesma maneira para casos ASCII e não ASCII. No entanto, a partir de 3,5 o caso ASCII é muito mais rápido.

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

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

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
Bhargav Rao
fonte
13
Este é um dos commits: github.com/python/cpython/commit/…
filmor
nota: casos ascii vs. não ascii podem diferir significativamente no desempenho. Não se trata de 55%: como mostra sua resposta, a velocidade pode ser de 1000s% .
jfs
compare: 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)"(não-ascii). O último é muito (10x) mais lento.
jfs
@JF Oh, agora entendi. Eu executei seu código para 3.4 e 3.5. Estou obtendo Py3.4 mais rápido para coisas não ASCII. É por coincidência? Os resultados dpaste.com/15FKSDQ
Bhargav Rao
Antes do 3.5, os casos ascii e não-ascii são provavelmente os mesmos para Unicode .translate(), ou seja, o caso ascii é muito mais rápido apenas no Python 3.5 (você não precisa bytes.translate()de desempenho lá).
jfs