gpt4 book ai didi

python - 类的实例使用哪些资源?

转载 作者:太空狗 更新时间:2023-10-29 17:11:16 24 4
gpt4 key购买 nike

为新创建的类实例分配资源时,python(我猜是 cpython)的效率如何?我有一种情况,我需要将节点类实例化数百万次以创建树结构。每个节点对象都应该是轻量级的,只包含一些数字以及对父节点和子节点的引用。

例如,python 是否需要为每个实例化对象的所有“双下划线”属性(例如文档字符串、 __dict____repr____class__ 等)分配内存,以单独创建这些属性或存储指向它们在类中定义的位置的指针?或者它是否高效并且除了我定义的需要存储在每个对象中的自定义内容之外不需要存储任何东西?

最佳答案

从表面上看,它非常简单:方法、类变量和类文档字符串都存储在类中(函数文档字符串存储在函数中)。实例变量存储在实例中。该实例还引用了该类,因此您可以查找方法。通常所有这些都存储在字典中(__dict__)。

所以是的,简短的回答是:Python 不在实例中存储方法,但所有实例都需要引用类。

例如,如果您有一个像这样的简单类:

class MyClass:
def __init__(self):
self.a = 1
self.b = 2

def __repr__(self):
return f"{self.__class__.__name__}({self.a}, {self.b})"

instance_1 = MyClass()
instance_2 = MyClass()

然后在内存中它看起来(非常简化)是这样的:

enter image description here

更深入

然而,在深入了解 CPython 时,有一些重要的事情:
  • 将字典作为抽象会导致相当多的开销:您需要引用实例字典(字节),字典中的每个条目都存储哈希(8 字节)、指向键的指针(8 字节)和指向存储属性(另外 8 个字节)。此外,字典通常会过度分配,以便添加另一个属性不会触发字典调整大小。
  • Python 没有“值类型”,即使是整数也会是一个实例。这意味着您不需要 4 个字节来存储整数 - Python 需要(在我的计算机上)24 个字节来存储整数 0,并且至少需要 28 个字节来存储不同于零的整数。然而,对其他对象的引用只需要 8 个字节(指针)。
  • CPython 使用引用计数,因此每个实例都需要一个引用计数(8 字节)。此外,大多数 CPythons 类都参与循环垃圾收集器,这会导致每个实例另外 24 字节的开销。除了这些可以被弱引用的类(大多数)之外,还有一个 __weakref__字段(另外 8 个字节)。

  • 在这一点上,还需要指出 CPython 针对其中一些“问题”进行了优化:
  • Python 使用 Key-Sharing Dictionaries以避免实例字典的一些内存开销(哈希和键)。
  • 您可以使用 __slots__在类里面避免__dict____weakref__ .这可以显着减少每个实例的内存占用。
  • Python 实习一些值,例如,如果您创建一个小整数,它不会创建一个新的整数实例,而是返回对已存在实例的引用。

  • 鉴于所有这些以及其中一些点(尤其是关于优化的点)是实现细节,很难给出关于 Python 类的有效内存要求的规范答案。

    减少实例的内存占用

    但是,如果您想减少实例的内存占用,请务必提供 __slots__一试。它们确实有缺点,但如果它们不适用于您,它们是减少内存的一种很好的方法。

    class Slotted:
    __slots__ = ('a', 'b')
    def __init__(self):
    self.a = 1
    self.b = 1

    如果这还不够,并且您使用大量“值类型”进行操作,您还可以更进一步并创建扩展类。这些是用 C 定义的类,但被包装起来以便您可以在 Python 中使用它们。

    为方便起见,我在这里使用 Cython 的 IPython 绑定(bind)来模拟扩展类:

    %load_ext cython

    %%cython

    cdef class Extensioned:
    cdef long long a
    cdef long long b

    def __init__(self):
    self.a = 1
    self.b = 1

    测量内存使用情况

    在所有这些理论之后,剩下的有趣问题是:我们如何测量内存力?

    我也使用一个普通的类:

    class Dicted:
    def __init__(self):
    self.a = 1
    self.b = 1

    我一般使用 psutil (即使它是一种代理方法)用于测量内存影响并简单地测量它前后使用了多少内存。测量值有点偏移,因为我需要以某种方式将实例保存在内存中,否则内存将被回收(立即)。此外,这只是一个近似值,因为 Python 实际上做了相当多的内存管理,尤其是在有大量创建/删除的情况下。


    import os
    import psutil
    process = psutil.Process(os.getpid())

    runs = 10
    instances = 100_000

    memory_dicted = [0] * runs
    memory_slotted = [0] * runs
    memory_extensioned = [0] * runs

    for run_index in range(runs):
    for store, cls in [(memory_dicted, Dicted), (memory_slotted, Slotted), (memory_extensioned, Extensioned)]:
    before = process.memory_info().rss
    l = [cls() for _ in range(instances)]
    store[run_index] = process.memory_info().rss - before
    l.clear() # reclaim memory for instances immediately

    每次运行的内存不会完全相同,因为 Python 会重新使用一些内存,有时还会为其他目的保留内存,但它至少应该给出一个合理的提示:
    >>> min(memory_dicted) / 1024**2, min(memory_slotted) / 1024**2, min(memory_extensioned) / 1024**2
    (15.625, 5.3359375, 2.7265625)

    我用了 min在这里主要是因为我对最小值是什么感兴趣,我除以 1024**2将字节转换为兆字节。

    总结:正如预期的那样,带有 dict 的普通类比带有槽的类需要更多的内存,但扩展类(如果适用且可用)的内存占用甚至更低。

    另一个可以非常方便地测量内存使用情况的工具是 memory_profiler ,虽然我已经有一段时间没有使用它了。

    关于python - 类的实例使用哪些资源?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56581237/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com