Разное время доступа к значению словаря при смешивании ключей int и str

#python #python-3.x #dictionary

Вопрос:

Допустим, у меня есть два словаря, и я знаю, что хочу измерить время, необходимое для проверки наличия ключа в словаре. Я попытался запустить этот фрагмент кода:

 from timeit import timeit

dct1 = {str(i): 1 for i in range(10**7)}
dct2 = {i: 1 for i in range(10**7)}

print(timeit('"7" in dct1', setup='from __main__ import dct1', number=10**8))
print(timeit('7 in dct2', setup='from __main__ import dct2', number=10**8))
 

Вот результаты, которые я получаю:

 2.529034548999334
2.212983401999736
 

Теперь предположим, что я пытаюсь смешать целые числа и строки в обоих словарях и снова измерить время доступа:

 dct1[7] = 1
dct2["7"] = 1

print(timeit('"7" in dct1', setup='from __main__ import dct1', number=10**8))
print(timeit('7 in dct1', setup='from __main__ import dct1', number=10**8))
print(timeit('7 in dct2', setup='from __main__ import dct2', number=10**8))
print(timeit('"7" in dct2', setup='from __main__ import dct2', number=10**8))
 

Я чувствую что-то странное:

 3.443614432000686
2.6335261530002754
2.1873921409987815
2.272667104998618
 

Первое значение намного выше, чем у меня было раньше (3,44 против 2,52). Однако третье значение в основном такое же, как и раньше (2,18 против 2,21). Почему это происходит? Вы можете воспроизвести то же самое или это только я? Кроме того, я не могу понять большой разницы между первым и вторым значением: похоже, что доступ к строковому ключу сложнее, но то же самое, похоже, лишь слегка применимо ко второму словарю. Почему?

Обновить

Вам даже не нужно добавлять новый ключ. Все, что вам нужно сделать, чтобы увидеть увеличение сложности, — это просто проверить, существует ли ключ другого типа!! Это гораздо более странно, чем я думал. Посмотрите на пример здесь:

 from timeit import timeit

dct1 = {str(i): 1 for i in range(10**7)}
dct2 = {i: 1 for i in range(10**7)}

print(timeit('"7" in dct1', setup='from __main__ import dct1', number=10**8))
# 2.55
print(timeit('7 in dct2', setup='from __main__ import dct2', number=10**8))
# 2.26

7 in dct1
"7" in dct2

print(timeit('"7" in dct1', setup='from __main__ import dct1', number=10**8))
# 3.34
print(timeit('7 in dct2', setup='from __main__ import dct2', number=10**8))
# 2.35
 

Комментарии:

1. Просто дикое предположение, но хэш int-это сам int, в то время как для str хэш, вероятно, должен быть вычислен, так что это может объяснить, почему поиск int происходит быстрее. Редактировать: хорошо, это не объясняет, почему "7" in dct1 это быстро.

2. @MikeScotty, Но я получаю доступ к точно такому же ключу до и после добавления другого ключа. Т. е. я всегда так делаю "7" in dct1 , я не меняю тип ключа. Кроме того, разница огромна

3. @MikeScotty "7" in dct2 должен быть таким же медленным, но это не так. Это так же быстро, как int.

4. Мех, ограничение в 5 минут редактирования может быть болезненным. Конечно, должно быть ok, it does not explain why "7" in **dct2** is fast. так сказано, я думаю, что моя догадка была ошибочной. Закладок 😉

5. Ну, кажется, я не могу добавить что-то к решению, но позвольте мне добавить что-то к проблеме: в моих тестах dct0 = {float(i): 1 for i in range(10**7)} взгляд вверх '7 in dct0' заметно медленнее, чем взгляд вверх '7 in dct1' , или '7 in dct2' в то время '"7" in dct0' как явно быстрее, чем '"7" in dct1'

Ответ №1:

Позвольте мне попытаться ответить на мой собственный вопрос. Реализация dict в CPython оптимизирована для поиска ключей str. Действительно, для выполнения поиска используются две разные функции:

  • lookdict это универсальная функция поиска по словарю, которая используется со всеми типами ключей
  • lookdict_unicode это специализированная функция поиска, используемая для словарей, состоящих только из ключей str

Python будет использовать оптимизированную для строк версию до тех пор, пока не будет выполнен поиск нестроковых данных, после чего будет использована более общая функция.

И похоже, что вы даже не можете изменить поведение конкретного экземпляра dict: как только он начнет использовать универсальную функцию, вы не сможете вернуться к использованию специализированной!