Почему str.translate намного быстрее в Python 3.5 по сравнению с Python 3.4?

116

Я пытался удалить ненужные символы из заданной строки, используя text.translate()Python 3.4.

Минимальный код:

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

Работает как положено. Однако одна и та же программа при выполнении в Python 3.4 и Python 3.5 дает большую разницу.

Код для расчета таймингов:

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

Программа Python 3.4 занимает 1,3 мс, тогда как та же программа в Python 3.5 занимает всего 26,4 мкс .

Что улучшено в Python 3.5, что делает его быстрее по сравнению с Python 3.4?

Бхаргав Рао
источник
11
Пока мы говорим о производительности, не лучше ли сгенерировать свой маппер следующим образом dict.fromkeys(ord(c) for c in '@#$'):?
Thomas K
1
@ThomasK Я обнаружил, что это имеет большое значение. Да по-твоему лучше.
Бхаргав Рао
Вы имели в виду в 50 раз быстрее?
assylias
@assylias Я сделал 1300 - 26,4, а затем разделил на 1300. Я получил почти 95%, поэтому я написал :) На самом деле это более чем в 50 раз быстрее ... Но ошибочен ли мой расчет? Я немного слаб в математике. Скоро выучу математику. :)
Бхаргав Рао
3
вы должны сделать это наоборот: 26/1300 = 2%, поэтому более быстрая версия занимает только 2% времени, затрачиваемого более медленной версией => она в 50 раз быстрее.
assylias

Ответы:

148

TL; DR - ВЫПУСК 21118


Длинная история

Джош Розенберг обнаружил, что эта str.translate()функция очень медленная по сравнению с bytes.translate, он поднял вопрос , заявив, что:

В Python 3 str.translate()обычно идет пессимизация производительности, а не оптимизация.

Почему было str.translate()медленно?

Основная причина str.translate()очень медленной работы заключалась в том, что поиск выполнялся в словаре Python.

Использование maketransусугубило эту проблему. Аналогичный подход с использованием bytesсоздает массив C из 256 элементов для быстрого поиска в таблице. Следовательно, использование Python более высокого уровня dictделает str.translate()Python 3.4 очень медленным.

Что сейчас произошло?

Первый подход заключался в добавлении небольшого патча translate_writer , однако увеличение скорости было не таким приятным. Вскоре был протестирован еще один патч fast_translate, который дал очень хорошие результаты - ускорение до 55%.

Основное изменение, как видно из файла, заключается в том, что поиск в словаре Python заменен поиском на уровне C.

Скорости сейчас почти такие же, как bytes

                                unpatched           patched

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

Небольшое замечание: повышение производительности заметно только в строках ASCII.

Как упоминает JFSebastian в комментарии ниже, до 3.5, translate работал одинаково как для ASCII, так и для случаев, отличных от ASCII. Однако с 3.5 ASCII дело намного быстрее.

Раньше ASCII и не-ascii были почти одинаковыми, но теперь мы можем видеть большие изменения в производительности.

Это может быть улучшение с 71,6 мкс до 2,33 мкс, как видно из этого ответа .

Следующий код демонстрирует это

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

Табулирование результатов:

         Python 3.4    Python 3.5  
Ascii     91.2          2.3 
Unicode   101           117
Бхаргав Рао
источник
13
Это один из
коммитов
Примечание: ascii и не-ascii могут значительно отличаться по производительности. Дело не в 55%: как показывает ваш ответ, скорость может быть 1000s% .
jfs
сравните: 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). Последний намного (в 10 раз) медленнее.
jfs
@JF О, теперь я понял это. Я запускал ваш код для версий 3.4 и 3.5. Я получаю Py3.4 быстрее для материалов, отличных от ascii. Случайно ли это? Результаты dpaste.com/15FKSDQ
Рао
До 3.5 случаи ascii и non-ascii, вероятно, были одинаковыми для Unicode, .translate()т.е. регистр ascii намного быстрее только в Python 3.5 (вам не нужна bytes.translate()производительность там).
jfs