Потребление памяти Python: dict VS список кортежей
Есть много вопросов и обсуждений о потреблении памяти разных типов данных python. Однако немногие из них (если таковые имеются) приходят к очень конкретному сценарию. Если вы хотите сохранить LOTS данных ключа в памяти, какая структура данных более эффективна с точки зрения памяти, dict или список кортежей?
В начале я думал, что dict более мощный, чем список кортежей, и что власть должна приходить с некоторой ценой, а на самом деле пустой dict DOES занимают больше памяти, чем пустой список или кортеж (см. Размер памяти в структуре Python), поэтому я подумал, что использование [(key1, value1), (key2, value2), ...]
будет более эффективным с точки зрения памяти, чем {key1: value1, key2: value2, ...}
.
Похоже, я ошибся. Просто запустите следующий фрагмент кода и просмотрите потребление памяти, указанное вашей ОС. Я использую Windows XP, чтобы диспетчер задач подсказывал мне, что большой диктат использует только "только" 40MB Ram и 40MB VIRTURAL Ram, но список кортежей питается 60MB Ram и 60MB Virtual ram.
Как это могло быть?
from sys import getsizeof as g
raw_input('ready, press ENTER')
i = 1000000
#p = [(x, x) for x in xrange(i)] # Will print 4,348,736 40,348,736
p = dict((x, x) for x in xrange(i)) # Will print 25,165,964 37,165,964
print g(p), g(p) + sum(g(x) for x in p)
raw_input("Check your process memory consumption now, press ENTER to exit")
Update:
Спасибо за некоторые из комментариев ниже. Я хочу уточнить: я говорю об эффективности памяти. И нет, в этом случае вам не нужно беспокоиться о эффективности поиска ключа-значения, просто предположим, что мой алгоритм будет потреблять их один за другим через итератор.
Ответы
Ответ 1
В list
tuple
добавлен дополнительный слой. У вас есть 3 слоя предметов:
- Внешний список длиной 1 миллион, поэтому 1 млн указателей
- 1 миллион 2-х слотовых кортежей, поэтому 2 миллиона указателей
- 2 миллиона ссылок на 1 миллион целых значений
в то время как ваш dict
выполняется только:
- The dict (включая 1 миллион кэшированных хэшей) с 2 миллионами указателей + дополнительное пространство для роста таблицы
- 2 миллиона ссылок на 1 миллион целых значений
Он содержит 1 миллион кортежей плюс список для хранения ссылок на них, которые занимают больше памяти, чем 1 миллион кэшированных хэшей. Здесь есть около 50% указателей, что позволяет легко использовать на 50% больше использования памяти.
Существует еще один недостаток вашего списка кортежей: время поиска. Чтобы найти подходящий ключ в dict, существует стоимость сложности O (1). Чтобы сделать то же самое в списке кортежей, вы должны потенциально просмотреть весь список для стоимости O (n). Не используйте список кортежей, если вам нужно сопоставить ключи со значениями.
Ответ 2
В этом случае вы получаете неполную картину использования памяти. Общий размер словаря более чем удваивается с нерегулярными интервалами, и если вы сравните размер этих двух структур сразу после того, как размер словаря увеличится, он снова станет больше. Простой script с рекурсивной функцией размера (см. Код ниже) показывает довольно четкий шаблон:
i: 2 list size: 296 dict size: 328 difference: -32
i: 3 list size: 392 dict size: 352 difference: 40
i: 4 list size: 488 dict size: 376 difference: 112
i: 5 list size: 616 dict size: 400 difference: 216
i: 7 list size: 808 dict size: 1216 difference: -408
i: 10 list size: 1160 dict size: 1288 difference: -128
i: 13 list size: 1448 dict size: 1360 difference: 88
i: 17 list size: 1904 dict size: 1456 difference: 448
i: 23 list size: 2480 dict size: 3904 difference: -1424
i: 31 list size: 3328 dict size: 4096 difference: -768
i: 42 list size: 4472 dict size: 4360 difference: 112
i: 56 list size: 5912 dict size: 4696 difference: 1216
i: 74 list size: 7880 dict size: 5128 difference: 2752
i: 100 list size: 10520 dict size: 14968 difference: -4448
i: 133 list size: 14024 dict size: 15760 difference: -1736
i: 177 list size: 18672 dict size: 16816 difference: 1856
Этот шаблон продолжается по мере роста i
. (Вы можете проверить это с помощью своего метода - попробуйте установить i
рядом с 2636744
. Размер словаря больше в этой точке, по крайней мере для меня.) Martijn правильно, что кортежи в списке кортежей добавляют к издержкам памяти, отменяя преимущество памяти в списках над словарями. Но результат, в среднем, не в том, что словарь лучше; это то, что словарь примерно одинаковый. Поэтому в ответ на ваш оригинальный вопрос:
Если вы хотите сохранить LOTS данных о значении ключа в памяти, какая структура данных более эффективна с точки зрения памяти, dict или список кортежей?
На самом деле неважно, все ли вы обеспокоены памятью.
Однако обратите внимание, что итерация по словарю часто немного медленнее, чем повторение по списку, потому что нет хорошего способа избежать итерации по всем пустым ячейкам в словаре. Таким образом, бит-словари намного быстрее при выполнении случайных ключевых поисков, но списки (бит) быстрее на итерации. Словарь, вероятно, будет лучше в большинстве случаев, но в некоторых редких случаях список может обеспечить микро-оптимизацию.
Вот код, который проверяет размер. Вероятно, он не даст правильных результатов для всех угловых случаев, но он должен без проблем справляться с такими простыми структурами. (Но дайте мне знать, если вы видите какие-либо проблемы.)
import sys, collections, itertools, math
def totalsize(x):
seen = set()
return ts_rec(x, seen)
def ts_rec(x, seen):
if id(x) in seen:
return 0
else:
seen.add(id(x))
x_size = sys.getsizeof(x)
if isinstance(x, collections.Mapping):
kv_chain = itertools.chain.from_iterable(x.iteritems())
return x_size + sum(ts_rec(i, seen) for i in kv_chain)
elif isinstance(x, collections.Sequence):
return x_size + sum(ts_rec(i, seen) for i in x)
else:
return x_size
for i in (10 ** (e / 8.0) for e in range(3, 19)):
i = int(i)
lsize = totalsize([(x, x) for x in xrange(i)])
dsize = totalsize(dict((x, x) for x in xrange(i)))
print "i: ", i,
print " list size: ", lsize, " dict size: ", dsize,
print " difference: ", lsize - dsize