У меня возникают трудности с пониманием (и в конечном итоге решением), почему наличие большого словаря в памяти делает создание других словарей дольше.
Здесь тестовый код, который я использую
import time
def create_dict():
# return {x:[x]*125 for x in xrange(0, 100000)}
return {x:(x)*125 for x in xrange(0, 100000)} # UPDATED: to use tuples instead of list of values
class Foo(object):
@staticmethod
def dict_init():
start = time.clock()
Foo.sample_dict = create_dict()
print "dict_init in Foo took {0} sec".format(time.clock() - start)
if __name__ == '__main__':
Foo.dict_init()
for x in xrange(0, 10):
start = time.clock()
create_dict()
print "Run {0} took {1} seconds".format(x, time.clock() - start)
Если я запускаю код как есть (сначала инициализируя sample_dict в Foo), а затем создавая тот же словарь еще 10 раз в цикле, я получаю следующие результаты:
dict_init in Foo took 0.385263764287 sec
Run 0 took 0.548807949139 seconds
Run 1 took 0.533209452471 seconds
Run 2 took 0.51916067636 seconds
Run 3 took 0.513130722575 seconds
Run 4 took 0.508272050029 seconds
Run 5 took 0.502263872177 seconds
Run 6 took 0.48867601998 seconds
Run 7 took 0.483109299676 seconds
Run 8 took 0.479019713488 seconds
Run 9 took 0.473174195256 seconds
[Finished in 5.6s]
Если, однако, я НЕ инициализирую sample_dict в Foo (комментируя Foo.dict_init()) Я получаю почти на 20% быстрее создание словаря в цикле
Run 0 took 0.431378921359 seconds
Run 1 took 0.423696636179 seconds
Run 2 took 0.419630475616 seconds
Run 3 took 0.405130343806 seconds
Run 4 took 0.398099686921 seconds
Run 5 took 0.392837169802 seconds
Run 6 took 0.38799598399 seconds
Run 7 took 0.375133006408 seconds
Run 8 took 0.368755297573 seconds
Run 9 took 0.363273701371 seconds
[Finished in 4.0s]
Я заметил, что если я выключу сборщик мусора Python, вызывая gc.disable(), производительность не только улучшает ~ 5x, но и хранение большого словаря в Foo не имеет значения. Ниже приведены результаты с отключенной сборкой мусора:
dict_init in Foo took 0.0696136982496 sec
Run 0 took 0.113533445358 seconds
Run 1 took 0.111091241489 seconds
Run 2 took 0.111151620212 seconds
Run 3 took 0.110655722831 seconds
Run 4 took 0.111807537706 seconds
Run 5 took 0.11097510318 seconds
Run 6 took 0.110936170451 seconds
Run 7 took 0.111074414632 seconds
Run 8 took 0.110678488579 seconds
Run 9 took 0.111011066463 seconds
У меня есть 2 вопроса:
- Почему отключение сборки мусора ускоряет создание словаря.
- Как достичь даже производительности (с помощью Foo init и без него) без отключения сборки мусора.
Буду признателен за это.
Спасибо!
ОБНОВЛЕНО: После того, как Тим Петерс упомянул, что я создаю изменяемые объекты, я решил попытаться создать неизменяемые объекты (кортежи в моем случае) и voila - гораздо более быстрые результаты (то же самое с использованием gc и без)
dict_init in Foo took 0.017769 sec
Run 0 took 0.017547 seconds
Run 1 took 0.013234 seconds
Run 2 took 0.012791 seconds
Run 3 took 0.013371 seconds
Run 4 took 0.013288 seconds
Run 5 took 0.013692 seconds
Run 6 took 0.013059 seconds
Run 7 took 0.013311 seconds
Run 8 took 0.013343 seconds
Run 9 took 0.013675 seconds
Я понимаю, что создание кортежей намного быстрее, чем создание списка, но почему использование словаря неизменных объектов не влияет на время, затраченное на сбор мусора? Являются ли неизменяемые объекты не задействованными в эталонном цикле?
Спасибо.
P.S. Как это бывает, в моем реальном сценарии преобразование списка в кортежи разрешило проблему (никогда не было необходимости иметь списки, просто не думал об использовании кортежей), но мне все еще интересно, почему это быстрее.