对 C 的标注是否可以预估 Python 字典的容量?
Can a callout to C presize a Python dict's capacity?
作为处理将容纳数千万或数亿个键的字典的优化,我真的非常想预先调整其容量……但似乎没有 Pythonic 的方法可以做到这一点。
用Cython或者C callout直接调用CPython的内部函数,比如dictresize() or _PyDict__NewPresized(),来实现这个是否实用?
通常,这是个坏主意,因为这意味着您的代码取决于 python 语言的实现。所以每次更新 CPython 时它都可能会中断。
但是,如果您提前知道键(这可能不会扩展到 python 的其他实现):
keys = {'red', 'green', 'blue', 'yellow', 'orange', 'pink', 'black'}
d = dict.fromkeys(keys)
这个字典d
will have the correct size right away,因为dict.fromkeys()提前分配了space
这取决于你所说的实用。这当然很简单;你可以打电话 _PyDict_NewPresized(howevermany)
。哎呀,你甚至可以从 Python:
>>> import ctypes
>>> import sys
>>> ctypes.pythonapi._PyDict_NewPresized.restype = ctypes.py_object
>>> d = ctypes.pythonapi._PyDict_NewPresized(100)
>>> sys.getsizeof(d)
1676
>>> sys.getsizeof({})
140
>>> len(d)
0
如您所见,dict 已预先调整大小,但没有任何元素。像这样依赖 CPython 实现细节是否实用取决于您。
经过一夜的黑客攻击,我想出了以下不依赖任何模块的解决方案。它允许您为最多 2**31-1 (=2,147,483,647) 个元素的任意数量的空间初始化一个字典。
def bigdict(size):
bytecode = '\x91%c%ci%c%cS'%((size>>16)&0xff,(size>>24)&0xff,size&0xff,(size>>8)&0xff)
return eval(bigdict.func_code.__class__( 0, 0, 1, 64, bytecode, (), (), (), "317070", '<module>', 1, '', (), ()))
举例说明:
In [95]: print sys.getsizeof({})
280
In [96]: print sys.getsizeof(bigdict(0))
280
In [97]: print sys.getsizeof(bigdict(1))
280
In [98]: print sys.getsizeof(bigdict(100))
3352
In [99]: print sys.getsizeof(bigdict(2**29-1))
12884902168
In [100]: print bigdict(2**29-1)
{}
这是我见过的最慢的空字典。最后一条命令花了很长时间才完成。
作为处理将容纳数千万或数亿个键的字典的优化,我真的非常想预先调整其容量……但似乎没有 Pythonic 的方法可以做到这一点。
用Cython或者C callout直接调用CPython的内部函数,比如dictresize() or _PyDict__NewPresized(),来实现这个是否实用?
通常,这是个坏主意,因为这意味着您的代码取决于 python 语言的实现。所以每次更新 CPython 时它都可能会中断。 但是,如果您提前知道键(这可能不会扩展到 python 的其他实现):
keys = {'red', 'green', 'blue', 'yellow', 'orange', 'pink', 'black'}
d = dict.fromkeys(keys)
这个字典d
will have the correct size right away,因为dict.fromkeys()提前分配了space
这取决于你所说的实用。这当然很简单;你可以打电话 _PyDict_NewPresized(howevermany)
。哎呀,你甚至可以从 Python:
>>> import ctypes
>>> import sys
>>> ctypes.pythonapi._PyDict_NewPresized.restype = ctypes.py_object
>>> d = ctypes.pythonapi._PyDict_NewPresized(100)
>>> sys.getsizeof(d)
1676
>>> sys.getsizeof({})
140
>>> len(d)
0
如您所见,dict 已预先调整大小,但没有任何元素。像这样依赖 CPython 实现细节是否实用取决于您。
经过一夜的黑客攻击,我想出了以下不依赖任何模块的解决方案。它允许您为最多 2**31-1 (=2,147,483,647) 个元素的任意数量的空间初始化一个字典。
def bigdict(size):
bytecode = '\x91%c%ci%c%cS'%((size>>16)&0xff,(size>>24)&0xff,size&0xff,(size>>8)&0xff)
return eval(bigdict.func_code.__class__( 0, 0, 1, 64, bytecode, (), (), (), "317070", '<module>', 1, '', (), ()))
举例说明:
In [95]: print sys.getsizeof({})
280
In [96]: print sys.getsizeof(bigdict(0))
280
In [97]: print sys.getsizeof(bigdict(1))
280
In [98]: print sys.getsizeof(bigdict(100))
3352
In [99]: print sys.getsizeof(bigdict(2**29-1))
12884902168
In [100]: print bigdict(2**29-1)
{}
这是我见过的最慢的空字典。最后一条命令花了很长时间才完成。