【问题标题】:Non-monotonic memory consumption in Python2 dictionariesPython2 字典中的非单调内存消耗
【发布时间】:2016-08-25 12:57:13
【问题描述】:

有人可以解释 CPython 2.7 中字典的这种非单调内存使用吗?

>>> import sys
>>> sys.getsizeof({})
280
>>> sys.getsizeof({'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
280
>>> sys.getsizeof({'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6})
1048
>>> sys.getsizeof({'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7})
1048
>>> sys.getsizeof({'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'e
ight': 8})
664
>>> sys.getsizeof({'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'e
ight': 8, 'nine': 9})
664

这里Python3是合理的,它将{'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7}的大小打印为480。

我在 Ubuntu 15.10 和 OS X 10.11 上试过这个。

【问题讨论】:

标签: python python-2.7 memory cpython python-internals


【解决方案1】:

TLDR:6 项和 7 项 dict 字面量预先调整哈希表的大小,然后在调整大小时将大小翻四倍。


当 CPython 2.7 计算 dict 文字时,在它开始填充条目之前,它用于创建 dict 的操作码是 BUILD_MAP。这需要一个参数,即 dict 将包含多少条目的提示,which it uses to presize the dict

    TARGET(BUILD_MAP)
    {
        x = _PyDict_NewPresized((Py_ssize_t)oparg);
        PUSH(x);
        if (x != NULL) DISPATCH();
        break;
    }

这是为了尽量减少 dict 在创建过程中调整大小的次数,但由于它们没有考虑负载因素,因此并不能完全消除调整大小。

正如source code comments 所指出的,_PyDict_NewPresized 旨在“创建一个预先调整大小以容纳估计数量的元素的新字典”。创建的 dict 中哈希表的确切大小受许多实现细节的影响,例如最小大小 (#define PyDict_MINSIZE 8) 和大小为 2 的幂的要求(以避免在实现中需要除法) .

对于最多 7 个条目的 dict 文字,_PyDict_NewPresized 初始化一个 8 个条目的哈希表;对于 8 个条目,它会初始化一个 16 个条目的哈希表,因为它使用的调整大小例程总是选择比参数更大的容量。


Dicts resize on insertion when they become at least 2/3 full. 对于 6 项和 7 项 dict 文字,该 dict 以 8 个条目开始,因此在第 6 次插入时会发生调整大小。 dict 足够小,resize 是哈希表大小的四倍:

return dictresize(mp, (mp->ma_used > 50000 ? 2 : 4) * mp->ma_used);

mp->ma_used是哈希表中使用的条目数,此时为6。 6 小于 50000,所以我们调用dictresize(mp, 4 * 6),它将哈希表的大小调整为 32 个条目,2 的最小幂大于 24。

相比之下,对于 8 条目 dict 文字,哈希表从 16 条目开始。 dict 在创建过程中不会变成 2/3,因此最初的 16 条目哈希表在 dict 创建过程中仍然存在,并且生成的 dict 小于 6 和 7 条目的 dict 文字。


Python 3 使用 different growth policy 以及其他 dict 实现更改,这就是您在 Python 3 中看到不同结果的原因。

【讨论】:

    【解决方案2】:

    好吧,我试了一下,让我们看看:

    dct = {'four': 3, 'three': 2, 'two': 1, 'one': 0}
    print(sys.getsizeof(dct))                             # = 272
    print(sys.getsizeof(dict(dct)))                       # = 272
    print(sys.getsizeof({k: v for k, v in dct.items()}))  # = 272
    
    dct = {'four': 3, 'three': 2, 'five': 4, 'two': 1, 'one': 0}
    print(sys.getsizeof(dct))                             # = 272
    print(sys.getsizeof(dict(dct)))                       # = 272
    print(sys.getsizeof({k: v for k, v in dct.items()}))  # = 272
    
    dct = {'six': 5, 'three': 2, 'two': 1, 'four': 3, 'five': 4, 'one': 0}
    print(sys.getsizeof(dct))                             # = 1040
    print(sys.getsizeof(dict(dct)))                       # = 656
    print(sys.getsizeof({k: v for k, v in dct.items()}))  # = 1040
    
    dct = {'seven': 6, 'six': 5, 'three': 2, 'two': 1, 'four': 3, 'five': 4, 'one': 0}
    print(sys.getsizeof(dct))                             # = 1040
    print(sys.getsizeof(dict(dct)))                       # = 656
    print(sys.getsizeof({k: v for k, v in dct.items()}))  # = 1040
    
    dct = {'seven': 6, 'six': 5, 'three': 2, 'two': 1, 'four': 3, 'five': 4, 'eight': 7, 'one': 0}
    print(sys.getsizeof(dct))                             # = 656
    print(sys.getsizeof(dict(dct)))                       # = 1040
    print(sys.getsizeof({k: v for k, v in dct.items()}))  # = 1040
    

    我不确定这里发生了什么样的优化,但我认为这是因为这些结构使用了不同的“最佳实践”。我的意思是何时为哈希表分配多少内存。例如,如果您有 11 个或更多元素,您会得到另一个奇怪的差异:

    dct = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10:10, 11:11}
    print(sys.getsizeof(dct))                             # = 1808
    print(sys.getsizeof(dict(dct)))                       # = 1040
    print(sys.getsizeof({k: v for k, v in dct.items()}))  # = 1040
    

    所以这可能只是以不同方式创建字典时的某种内存消耗“优化”,为什么在使用 6 或 7 个元素时文字语法会出现这种非单调异常值:我不知道。也许一些内存优化出错了,它分配了太多内存是一个错误?我还没有阅读源代码(还)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-05-29
      • 2021-01-10
      • 2014-09-19
      • 2010-10-12
      • 1970-01-01
      • 2012-03-15
      • 1970-01-01
      相关资源
      最近更新 更多