Удивительные результаты с Python timeit: Counter() против defaultdict() против dict ()
Я получил очень удивительные результаты со временем, может кто-нибудь сказать мне, если я делаю что-то не так? Я использую Python 2.7.
Это содержимое файла speedtest_init.py:
import random
to_count = [random.randint(0, 100) for r in range(60)]
Это содержимое speedtest.py:
__author__ = 'BlueTrin'
import timeit
def test_init1():
print(timeit.timeit('import speedtest_init'))
def test_counter1():
s = """\
d = defaultdict(int);
for i in speedtest_init.to_count:
d[i] += 1
"""
print(timeit.timeit(s, 'from collections import defaultdict; import speedtest_init;'))
def test_counter2():
print(timeit.timeit('d = Counter(speedtest_init.to_count);', 'from collections import Counter; import speedtest_init;'))
if __name__ == "__main__":
test_init1()
test_counter1()
test_counter2()
Консольный вывод:
C:\Python27\python.exe C:/Dev/codility/chlorum2014/speedtest.py
2.71501962931
65.7090444503
91.2953839048
Process finished with exit code 0
Я думаю, что по умолчанию timeit() запускает 1000000 раз код, поэтому мне нужно разделить время на 1000000, но что удивительно, так это то, что Counter медленнее, чем defaultdict().
Это ожидается?
РЕДАКТИРОВАТЬ:
Также использование dict быстрее, чем defaultdict(int):
def test_counter3():
s = """\
d = {};
for i in speedtest_init.to_count:
if i not in d:
d[i] = 1
else:
d[i] += 1
"""
print(timeit.timeit(stmt=s, setup='from collections import defaultdict; import speedtest_init;')
эта последняя версия быстрее, чем defaultdict (int), что означает, что если вы не заботитесь о читабельности, вы должны использовать dict (), а не defaultdict().
1 ответ
Да, это ожидается; Counter()
конструктор использует Counter.update()
который использует self.get()
загружать начальные значения, а не полагаться на __missing__
,
Кроме того, defaultdict
__missing__
Фабрика полностью обрабатывается в C-коде, особенно при использовании другого типа, такого как int()
это само реализовано в C. Counter
источник чистый Python и как таковой Counter.__missing__
Метод требует фрейма Python для выполнения.
Так как dict.get()
все еще обрабатывается в C, подход конструктора является более быстрым подходом для Counter()
при условии, что вы используете тот же трюк Counter.update()
использует и псевдоним self.get
поиск как местный сначала:
>>> import timeit
>>> import random
>>> to_count = [random.randint(0, 100) for r in range(60)]
>>> timeit.timeit('for i in to_count: c[i] += 1',
... 'from collections import Counter; from __main__ import to_count; c = Counter()',
... number=10000)
0.2510359287261963
>>> timeit.timeit('for i in to_count: c[i] = c_get(i, 0) + 1',
... 'from collections import Counter; from __main__ import to_count; c = Counter(); c_get = c.get',
... number=10000)
0.20978617668151855
И то и другое defaultdict
а также Counter
полезные классы построены для их функциональности, а не для их производительности; не полагаясь на __missing__
крючок может быть еще быстрее:
>>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1',
... 'from __main__ import to_count; d = {}; d_get = d.get',
... number=10000)
0.11437392234802246
Это обычный словарь с использованием псевдонима dict.get()
метод для максимальной скорости. Но тогда вам также придется заново реализовать поведение сумки Counter
, или Counter.most_common()
метод. defaultdict
случаи использования выходят далеко за рамки подсчета.
В Python 3.2 обновление Counter()
получил ускорение за счет добавления библиотеки C, которая обрабатывает этот случай; см. выпуск 10667. Тестирование на Python 3.4 Counter()
конструктор теперь бьет псевдоним dict.get
дело:
>>> timeit.timeit('Counter(to_count)',
... 'from collections import Counter; from __main__ import to_count',
... number=100000)
0.8332311600097455
>>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1',
... 'from __main__ import to_count; d = {}; d_get = d.get',
... number=100000)
0.961191965994658