Подтвердить что ты не робот

PEP 412 делает __slots__ избыточным?

PEP 412, реализованный в Python 3.3, представляет собой улучшенную обработку словарей атрибутов, что эффективно уменьшает объем памяти экземпляров класса. __slots__ был разработан с той же целью, так что есть ли смысл использовать __slots__ больше?

В попытке самостоятельно выяснить ответ, я запускаю следующий тест, но результаты не имеют большого смысла:

class Slots(object):
    __slots__ = ['a', 'b', 'c', 'd', 'e']
    def __init__(self):
        self.a = 1
        self.b = 1
        self.c = 1
        self.d = 1
        self.e = 1  

class NoSlots(object):
    def __init__(self):
        self.a = 1
        self.b = 1
        self.c = 1
        self.d = 1
        self.e = 1

Python 3.3 Результаты:

>>> sys.getsizeof([Slots() for i in range(1000)])
Out[1]: 9024
>>> sys.getsizeof([NoSlots() for i in range(1000)])
Out[1]: 9024

Результаты Python 2.7:

>>> sys.getsizeof([Slots() for i in range(1000)])
Out[1]: 4516
>>> sys.getsizeof([NoSlots() for i in range(1000)])
Out[1]: 4516

Я бы ожидал, что размер будет отличаться, по крайней мере, для Python 2.7, поэтому я предполагаю, что что-то не так с тестом.

4b9b3361

Ответ 1

Нет, PEP 412 не делает __slots__ избыточным.


Во-первых, Армин Риго прав, что вы не правильно его измеряете. Для измерения нужно указать размер объекта плюс значения, а также __dict__ (только для NoSlots) и ключи (только для NoSlots).

Или вы можете делать то, что он предлагает:

cls = Slots if len(sys.argv) > 1 else NoSlots
def f():
    tracemalloc.start()
    objs = [cls() for _ in range(100000)]
    print(tracemalloc.get_traced_memory())
f()

Когда я запускаю это на 64-разрядном CPython 3.4 в OS X, я получаю 8824968 для NoSlots и 25624872 для Slots. Итак, похоже, что экземпляр NoSlots занимает 88 байт, а экземпляр Slots занимает 256 байт.


Как это возможно?

Поскольку между __slots__ и разделом клавиш __dict__ существуют еще две отличия.

Сначала хеш-таблицы, используемые словарями, поддерживаются ниже 2/3rds, и они растут экспоненциально и имеют минимальный размер, поэтому у вас будет дополнительное пространство. И не сложно определить, сколько места, глядя на красиво прокомментированный источник : у вас будет 8 хэш-кодов вместо 5 слотов.

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

И, конечно же, в вашем примере значения, поэтому единственная стоимость здесь - это сам объект, плюс 5 слотов или 8 хэш-кодов и заголовок dict, поэтому разница довольно драматична. В реальной жизни __slots__ редко будет такой значительной выгодой.


Наконец, обратите внимание, что PEP 412 только утверждает:

Бенчмаркинг показывает, что использование памяти сокращается на 10% до 20% для объектно-ориентированных программ

Подумайте, где вы используете __slots__. Либо сбережения настолько огромны, что не использовать __slots__ было бы смешно, или вам действительно нужно выжать последние 15%. Или вы строите ABC или другой класс, который, как вы ожидаете, будет подклассифицирован кем-то, что и подклассы, возможно, потребуют экономии. Во всяком случае, в тех случаях тот факт, что вы получаете половину пособия без __slots__, или даже две трети выгоды, по-прежнему редко будет достаточным; вам все равно придется использовать __slots__.

Настоящая победа в тех случаях, когда не стоит использовать __slots__; вы получите небольшое пособие бесплатно.

(Кроме того, есть определенные программисты, которые чрезмерно используют ад из __slots__, и, может быть, это изменение может убедить некоторых из них включить свою энергию в микро-оптимизацию чего-то еще не столь неулокального, если вам повезет. )

Ответ 2

Проблема sys.getsizeof(), которая редко возвращает то, что вы ожидаете. Например, в этом случае он подсчитывает "размер" объекта без учета размера его __dict__. Я предлагаю вам повторить попытку, измеряя реальное использование памяти при создании 100 000 экземпляров.

Отметим также, что поведение Python 3.3 было вдохновлено PyPy, в котором __slots__ не имеет никакого значения, поэтому я ожидаю, что это тоже не повлияет на Python 3.3. Насколько я могу судить, __slots__ почти никогда не используется.