У меня есть объект, который нужно пометить 0-3 строками (из набора из 20 возможных вариантов); все эти значения уникальны, и порядок не имеет значения. Единственная операция, которую необходимо выполнить с тегами, - это проверка наличия или отсутствия конкретной операции (specific_value in self.tags).

Тем не менее, в памяти одновременно находится огромное количество этих объектов, вплоть до того, что это расширяет границы ОЗУ моего старого компьютера. Таким образом, можно сэкономить несколько байтов.

С таким небольшим количеством тегов на каждом объекте, я сомневаюсь, что время поиска будет иметь большое значение. Но: есть ли разница в памяти между использованием кортежа и фрозенсе здесь? Есть ли другая реальная причина использовать один поверх другого?

5
Draconis 7 Июл 2019 в 07:44

3 ответа

Лучший ответ

Кортежи очень компактны. Наборы основаны на хеш-таблицах и зависят от наличия «пустых» слотов, чтобы сделать хеш-коллизии менее вероятными.

Для достаточно свежей версии CPython отображается sys._debugmallocstats() много потенциально интересной информации. Здесь под 64-битным Python 3.7.3:

>>> from sys import _debugmallocstats as d
>>> tups = [tuple("abc") for i in range(1000000)]

tuple("abc") создает кортеж из 3 1-символьных строк, ('a', 'b', 'c'). Здесь я отредактирую почти весь вывод:

>>> d()
Small block threshold = 512, in 64 size classes.

class   size   num pools   blocks in use  avail blocks
-----   ----   ---------   -------------  ------------
...
    8     72       17941         1004692             4

Поскольку мы создали миллион кортежей, очень хорошо, что класс размера, использующий 1004692 блока, - это тот, который нам нужен ;-) Каждый из блоков потребляет 72 байта.

Вместо этого, переключаясь на frozensets, вывод показывает, что они потребляют 224 байта каждый, что более чем в 3 раза больше:

>>> tups = [frozenset(t) for t in tups]
>>> d()
Small block threshold = 512, in 64 size classes.

class   size   num pools   blocks in use  avail blocks
-----   ----   ---------   -------------  ------------
...
   27    224       55561         1000092             6

В этом конкретном случае другой полученный вами ответ дает те же результаты:

>>> import sys
>>> sys.getsizeof(tuple("abc"))
72
>>> sys.getsizeof(frozenset(tuple("abc")))
224

Хотя это часто так, это не всегда так, потому что объект может потребовать выделять больше байтов, чем на самом деле, чтобы удовлетворить требования выравнивания HW. getsizeof() ничего об этом не знает, но _debugmallocstats() показывает количество байтов, которое фактически должен использовать распределитель небольших объектов Python.

Например,

>>> sys.getsizeof("a")
50

На 32-битном блоке фактически необходимо использовать 52 байта, чтобы обеспечить 4-байтовое выравнивание. В 64-битном боксе в настоящее время требуется 8-байтовое выравнивание, поэтому необходимо использовать 56 байтов. В Python 3.8 (еще не выпущен) для 64-битного блока требуется 16-байтовое выравнивание, и необходимо использовать 64 байта.

Но, игнорируя все это, кортежу всегда потребуется меньше памяти, чем любой форме набора с таким же количеством элементов - и даже меньше, чем список с таким же количеством элементов.

5
Boris 17 Янв 2020 в 02:18

sys.getsizeof выглядит как stdlib вариант, который вы хотите ... но я чувствую тошноту по поводу всего вашего варианта использования

import sys
t = ("foo", "bar", "baz")
f = frozenset(("foo","bar","baz"))
print(sys.getsizeof(t))
print(sys.getsizeof(f))

https://docs.python.org/3.7/library/sys.html#sys.getsizeof

Все встроенные объекты будут возвращать правильные результаты, но это не должно выполняться для сторонних расширений, поскольку это зависит от реализации.

... Так что не будьте удобны с этим решением

РЕДАКТИРОВАТЬ: Очевидно, ответ @TimPeters является более правильным ...

4
Charles Landau 7 Июл 2019 в 05:45

Если вы пытаетесь сохранить память, подумайте

  • Отменяя некоторую элегантность для некоторой экономии памяти, извлекая структуру данных, из которых присутствуют теги, во внешнюю (одноэлементную) структуру данных
  • Использование подхода типа «флаги» (растровое изображение), в котором каждый тег сопоставляется с битом 32-разрядного целого числа. Тогда все, что вам нужно - это отображение (singleton) dict объекта (идентификатора) в 32-разрядное целое число (флаги). Если нет флагов, нет записи в словаре.
2
eddiewould 7 Июл 2019 в 05:09