我跑了这个:
import sys
diii = {'key1':1,'key2':2,'key3':1,'key4':2,'key5':1,'key6':2,'key7':1}
print sys.getsizeof(diii)
# output: 1048
diii = {'key1':1,'key2':2,'key3':1,'key4':2,'key5':1,'key6':2,'key7':1,'key8':2}
print sys.getsizeof(diii)
# output: 664
在这里问之前,我重新启动了python shell并在线进行了尝试,并得到了相同的结果。
我认为一本包含更多元素的字典将提供与输出相同或更多的字节,而不是包含少一个元素的字典。
知道我在做什么错吗?
先前的答案已经提到您不必担心,因此我将深入探讨一些技术细节。很长,但是请忍受我。
TLDR:这与调整大小的算法有关。每次调整大小都会分配2**i
内存,其中2**i > requested_size; 2**i >= 8
,但是如果填充了2/3的插槽,则每次插入都会进一步调整基础表的大小,但这一次是new_size = old_size * 4
。这样,您的第一个字典最终分配了32个单元格,而第二个字典仅分配了16个单元格(因为它的初始大小更大)。
答:正如@snakecharmerb在评论中指出的那样,这取决于字典的创建方式。为了简洁起见,让我向您推荐这篇优秀的博客文章,该文章解释了Python字节码和CPython实现级别上的dict()
构造函数和dict文字之间的区别{}
。
让我们从8键的神奇数字开始。事实证明,这是为dictobject.h头文件中的Python 2.7实现预定义的常量-Python字典的最小大小:
/* PyDict_MINSIZE is the minimum size of a dictionary. This many slots are
* allocated directly in the dict object (in the ma_smalltable member).
* It must be a power of 2, and at least 4. 8 allows dicts with no more
* than 5 active entries to live in ma_smalltable (and so avoid an
* additional malloc); instrumentation suggested this suffices for the
* majority of dicts (consisting mostly of usually-small instance dicts and
* usually-small dicts created to pass keyword arguments).
*/
#define PyDict_MINSIZE 8
因此,在特定的Python实现之间可能有所不同,但让我们假设我们都使用相同的CPython版本。但是,大小为8的dict预计将仅包含5个元素;不必担心,因为这种特定的优化对我们而言似乎并不重要。
现在,当您使用dict文字创建字典时{}
,CPython会采用快捷方式(与调用dict
构造函数时的显式创建相比)。字节码操作简化了一点BUILD_MAP
,它导致调用该_PyDict_NewPresized
函数,该函数将构造一个我们已经预先知道其大小的字典:
/* Create a new dictionary pre-sized to hold an estimated number of elements.
Underestimates are okay because the dictionary will resize as necessary.
Overestimates just mean the dictionary will be more sparse than usual.
*/
PyObject *
_PyDict_NewPresized(Py_ssize_t minused)
{
PyObject *op = PyDict_New();
if (minused>5 && op != NULL && dictresize((PyDictObject *)op, minused) == -1) {
Py_DECREF(op);
return NULL;
}
return op;
}
该函数调用普通的dict构造函数(PyDict_New
),并要求重新调整新创建的dict的大小-但前提是希望它容纳5个以上的元素。这是由于一项优化,它允许Python通过将数据保存在预先分配的“小表”中来加速某些事情,而无需调用昂贵的内存分配和取消分配功能。
然后,dictresize
将尝试确定新字典的最小大小。它还将使用魔术数8-作为起点,并迭代乘以2,直到找到最小大小大于请求的大小。对于第一个字典,它只是8,但是,对于第二个字典(以及由dict文字少于15个键的所有字典),它是16。
现在,在该dictresize
函数中,前者具有一个特殊情况new_size == 8
,即small,这意味着进行上述优化(使用“小表”减少内存操作)。但是,由于不需要调整新创建的dict的大小(例如,到目前为止尚未删除任何元素,因此表是“干净的”),因此实际上什么也没有发生。
相反,当时new_size != 8
,遵循重新分配哈希表的常规过程。最后,分配一个新表来存储“大”字典。尽管这是直观的(较大的dict有较大的表),但这似乎尚未使我们前进到所观察到的行为-但是,请再忍受我一下。
一旦有了预分配的字典,STORE_MAP optcodes就会告诉解释器插入连续的键值对。这是通过dict_set_item_by_hash_or_entry
功能实现的-重要的是-如果已经使用了超过2/3的插槽,则每次增加大小(即成功插入)后都会调整字典的大小。大小将增加x4(在我们的示例中,对于较大的字典,仅增加x2)。
因此,当您使用7个元素创建字典时会发生以下情况:
# note 2/3 = 0.(6)
BUILD_MAP # initial_size = 8, filled = 0
STORE_MAP # 'key_1' ratio_filled = 1/8 = 0.125, not resizing
STORE_MAP # 'key_2' ratio_filled = 2/8 = 0.250, not resizing
STORE_MAP # 'key_3' ratio_filled = 3/8 = 0.375, not resizing
STORE_MAP # 'key_4' ratio_filled = 4/8 = 0.500, not resizing
STORE_MAP # 'key_5' ratio_filled = 5/8 = 0.625, not resizing
STORE_MAP # 'key_6' ratio_filled = 6/8 = 0.750, RESIZING! new_size = 8*4 = 32
STORE_MAP # 'key_7' ratio_filled = 7/32 = 0.21875
最后,您得到的哈希表中共有32个元素的dict。
但是,当添加八个元素时,初始大小将是其两倍大(16),因此我们将永远不会调整大小,因为ratio_filled > 2/3
将永远无法满足条件!
这就是为什么在第二种情况下最终使用较小的表的原因。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句