Потребление памяти Python: список кортежей dict VS

Существует множество вопросов и дискуссий о потреблении памяти различными типами данных Python. Тем не менее, немногие из них (если таковые имеются) приходят к очень конкретному сценарию. Если вы хотите хранить в памяти МНОГО данных ключ-значение, какая структура данных более эффективна с точки зрения памяти, словарь или список кортежей?

Вначале я думал, что dict более мощный, чем список кортежей, и эта сила должна иметь некоторую цену, и на самом деле пустой dict ДЕЙСТВИТЕЛЬНО занимает больше памяти, чем пустой список или кортеж (см. Размер структуры Python в памяти), поэтому я подумал, что использование [(key1, value1), (key2, value2), ...] будет более эффективно с точки зрения памяти, чем {key1: value1, key2: value2, ...}.

Похоже, я ошибался. Просто запустите следующий фрагмент кода и посмотрите, сколько памяти потребляет ваша ОС. Я использую Windows XP, поэтому диспетчер задач сообщает мне, что большой диктатор съедает «только» 40 МБ ОЗУ и 40 МБ ВИРТУРНОЙ ОЗУ, но список кортежей съедает 60 МБ ОЗУ и 60 МБ виртуального ОЗУ.

Как такое могло быть?

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's memory consumption now, press ENTER to exit")

Обновление:

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


person RayLuo    schedule 26.03.2013    source источник
comment
Вы задаете неправильный вопрос. Если вам нужен поиск пары "ключ-значение", используйте dict. Если вам нужен массив, используйте список или кортеж.   -  person Hai Vu    schedule 26.03.2013
comment
Python ведет хеш-таблицу для словарей. Эта ссылка была из другой ответ Значит, словари быстрее ищут, а кортежи используют меньше памяти.   -  person mbowden    schedule 26.03.2013
comment
Для некоторых типов данных вы можете использовать что-то даже более оптимальное, чем оба ваших варианта, например дерево.   -  person wRAR    schedule 26.03.2013
comment
Эффективен для чего? Для эффективного использования памяти или для быстрого поиска?   -  person Brian Neal    schedule 26.03.2013


Ответы (2)


Ваш list из tuples добавляет дополнительный слой. У вас есть 3 уровня элементов:

  • The outer list of length 1 million, so 1 million pointers
    • 1 million 2-slot tuples, so 2 million pointers
      • 2 million references to 1 million integer values

в то время как ваш dict содержит только:

  • The dict (including 1 million cached hashes) with 2 million pointers + extra space to grow the table
    • 2 million references to 1 million integer values

Эти 1 миллион кортежей плюс список для хранения ссылок на них занимают больше памяти, чем 1 миллион кэшированных хэшей. Здесь задействовано примерно на 50% больше указателей, что легко объясняет увеличение использования памяти на 50%, которое вы видите.

У вашего списка кортежей есть еще один недостаток: время поиска. Чтобы найти соответствующий ключ в dict, требуется стоимость сложности O (1). Чтобы сделать то же самое в списке кортежей, вы должны потенциально просканировать весь список на предмет стоимости O (n). Не используйте список кортежей, если вам нужно сопоставить ключи со значениями.

person Martijn Pieters    schedule 26.03.2013
comment
Я думаю, вы правы насчет дополнительных слоев. Как вы думаете, в этом случае dict по-прежнему наиболее эффективен с точки зрения памяти, даже если я просто хочу хранить эти данные? (Предположим, мне не нужен случайный поиск.) - person RayLuo; 26.03.2013
comment
@ Айсберг: я бы не стал хранить данные. Если вы не ищите в нем ничего особенного, какой в ​​этом смысл? Вы также можете использовать плоский кортеж, чтобы не было вложения пар; вы все еще можете легко воссоздать пары. - person Martijn Pieters; 26.03.2013
comment
Я хочу держать их, а затем повторять. Уловка с плоскими кортежами может помочь ценой потери читабельности. - person RayLuo; 26.03.2013
comment
@Iceberg: Попарная итерация: Итерация по каждым двум элементам в списке - person Martijn Pieters; 26.03.2013

На самом деле в этом случае вы получаете неполную картину использования памяти. Общий размер словаря увеличивается более чем вдвое с нерегулярными интервалами, и если вы сравните размер этих двух структур сразу после увеличения размера словаря, он снова станет больше. Простой скрипт с функцией рекурсивного размера (см. Код ниже) показывает довольно четкую закономерность:

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 прав в том, что кортежи в списке кортежей увеличивают накладные расходы памяти, сводя на нет преимущество списков над словарями в памяти. Но результат, в среднем, не означает, что словарь лучше; дело в том, что словарь примерно такой же. Итак, в ответ на ваш исходный вопрос:

Если вы хотите хранить в памяти МНОГО данных ключ-значение, какая структура данных более эффективна с точки зрения памяти, словарь или список кортежей?

На самом деле это не имеет значения, если все, что вас беспокоит, - это память.

Однако обратите внимание, что итерация по словарю часто немного медленнее, чем по списку, потому что нет хорошего способа избежать итерации по всем пустым ячейкам в словаре. Так что есть небольшой компромисс - словари (намного) быстрее выполняют поиск случайных ключей, но списки (немного) быстрее при итерации. Словарь, вероятно, будет лучше в большинстве случаев, но в некоторых редких случаях список может обеспечить микрооптимизацию.


Вот код, который проверяет размер. Вероятно, он не будет генерировать правильные результаты для всех угловых случаев, но он должен обрабатывать простые структуры, подобные этой, без каких-либо проблем. (Но дайте мне знать, если вы заметите какие-либо проблемы.)

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
person senderle    schedule 26.03.2013
comment
Цените вашу попытку помочь. По крайней мере, вы понимаете исходный вопрос намного лучше, чем те, кто комментирует мой вопрос. Насколько я понимаю, это зависит ... от того, сколько элементов нужно хранить, и нам лучше иметь контрольный показатель, если длина известна и фиксирована. Кстати, я изменяю ваш скрипт как for i in (10 ** (e /8.0) for e in range(3, 49)): и вижу, что dict всегда превосходит список кортежей, когда i = 42169, 56234, 74989, ... вплоть до i = 1000000. О, спасибо за упоминание скорости итерации. - person RayLuo; 27.03.2013
comment
@ Айсберг, да, я об этом и имел в виду. Но я бы добавил, что, если вы не проводите серьезную микрооптимизацию, тест на самом деле не стоит проблем; используйте структуру, которая имеет практический смысл для вашей проблемы. А с другой стороны, если вы выполняете микрооптимизацию и не заботитесь о доступе с произвольным ключом, то, вероятно, вы получите лучший результат от плоского списка, как предлагает Мартин. - person senderle; 27.03.2013
comment
Говоря о микрооптимизации, люди могут намекнуть на недостойные усилия? Но в этом случае, ориентированном на память, разница плюс / минус может составлять от 2% до 71%. Это ОЧЕНЬ знаменательно! Кроме того, dict семантически похож на список кортежей, но не на плоский список. В общем, теперь мы знаем все «за» и «против», поэтому можем выбрать любой из них, когда сочтем его подходящим в определенной ситуации. Спасибо всем, кто вносит свой вклад в эту ветку! - person RayLuo; 28.03.2013