python - Python 中(命名)元组的字典和速度/RAM 性能
问题描述
我正在创建一个d
包含一百万个元组项目的字典,理想情况下我想通过以下方式访问它们:
d[1634].id # or d[1634]['id']
d[1634].name # or d[1634]['name']
d[1634].isvalid # or d[1634]['isvalid']
而不是d[1634][0]
, d[1634][1]
,d[1634][2]
后者不太明确。
根据我的测试:
import os, psutil, time, collections, typing
Tri = collections.namedtuple('Tri', 'id,name,isvalid')
Tri2 = typing.NamedTuple("Tri2", [('id', int), ('name', str), ('isvalid', bool)])
t0 = time.time()
# uncomment only one of these 4 next lines:
d = {i: (i+1, 'hello', True) for i in range(1000000)} # tuple
# d = {i: {'id': i+1, 'name': 'hello', 'isvalid': True} for i in range(1000000)} # dict
# d = {i: Tri(id=i+1, name='hello', isvalid=True) for i in range(1000000)} # namedtuple
# d = {i: Tri2(id=i+1, name='hello', isvalid=True) for i in range(1000000)} # NamedTuple
print('%.3f s %.1f MB' % (time.time()-t0, psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2))
"""
tuple: 0.257 s 193.3 MB
dict: 0.329 s 363.6 MB
namedtuple: 1.253 s 193.3 MB (collections)
NamedTuple: 1.250 s 193.5 MB (typing)
"""
dict
与 a 相比,使用 a会使 RAM 使用量翻倍tuple
- 与 a 相比,使用 a
namedtuple
或NamedTuple
乘以 5 所花费的时间tuple
!
x.id
问题:Python 3 中是否有类似元组的数据结构,它允许使用、等访问数据,x.name
并且 RAM 和 CPU 高效?
笔记:
在我的实际用例中,
tuple
它类似于 type 的 C-struct(uint64, uint64, bool)
。我也尝试过:
slots
(要避免内部对象的__dict__
,请参阅__slots__ 的用法?)dataclass
:@dataclasses.dataclass class Tri3: id: int ...
ctypes.Structure
:class Tri7(ctypes.Structure): _fields_ = [("id", ctypes.c_int), ...]
但它并没有更好(所有这些都〜1.2秒),
tuple
在性能方面没有接近真正的以下是其他选项:Python 中的类 C 结构
解决方案
Cython 的cdef 类可能是您想要的:它们比纯 Python 类使用更少的内存,即使在访问成员时会付出更多开销(因为字段存储为 C 值而不是 Python 对象)。
例如:
%%cython
cdef class CTuple:
cdef public unsigned long long int id
cdef public str name
cdef public bint isvalid
def __init__(self, id, name, isvalid):
self.id = id
self.name = name
self.isvalid = isvalid
可以根据需要使用:
ob=CTuple(1,"mmm",3)
ob.id, ob.name, ob.isvalid # prints (2, "mmm", 3)
计时/内存消耗:
首先,我机器上的基线:
0.258 s 252.4 MB # tuples
0.343 s 417.5 MB # dict
1.181 s 264.0 MB # namedtuple collections
CTuple
我们得到:
0.306 s 191.0 MB
这几乎一样快,并且需要相当少的内存。
如果 C 类型的成员在编译时不清楚,可以使用简单的 python 对象:
%%cython
cdef class PTuple:
cdef public object id
cdef public object name
cdef public object isvalid
def __init__(self, id, name, isvalid):
self.id = id
self.name = name
self.isvalid = isvalid
时间安排有点令人惊讶:
0.648 s 249.8 MB
没想到它比CTuple
-version慢这么多,但至少比命名元组快两倍。
这种方法的一个缺点是它需要编译。然而,Cython 提供cython.inline
了可用于编译即时创建的 Cython 代码的功能。
我已经发布cynamedtuple
了可以通过 安装的pip install cynamedtuple
,并且基于下面的原型:
import cython
# for generation of cython code:
tab = " "
def create_members_definition(name_to_ctype):
members = []
for my_name, my_ctype in name_to_ctype.items():
members.append(tab+"cdef public "+my_ctype+" "+my_name)
return members
def create_signature(names):
return tab + "def __init__(self,"+", ".join(names)+"):"
def create_initialization(names):
inits = [tab+tab+"self."+x+" = "+x for x in names]
return inits
def create_cdef_class_code(classname, names):
code_lines = ["cdef class " + classname + ":"]
code_lines.extend(create_members_definition(names))
code_lines.append(create_signature(names.keys()))
code_lines.extend(create_initialization(names.keys()))
return "\n".join(code_lines)+"\n"
# utilize cython.inline to generate and load pyx-module:
def create_cnamedtuple_class(classname, names):
code = create_cdef_class_code(classname, names)
code = code + "GenericClass = " + classname +"\n"
ret = cython.inline(code)
return ret["GenericClass"]
可以使用如下,CTuple
从上面动态定义:
CTuple = create_cnamedtuple_class("CTuple",
{"id":"unsigned long long int",
"name":"str",
"isvalid":"bint"})
ob = CTuple(1,"mmm",3)
...
另一种选择是使用 jit 编译和 Numba 的 jit -classes,它们提供了这种可能性。然而,它们似乎要慢得多:
from numba import jitclass, types
spec = [
('id', types.uint64),
('name', types.string),
('isvalid', types.uint8),
]
@jitclass(spec)
class NBTuple(object):
def __init__(self, id, name, isvalid):
self.id = id
self.name = name
self.isvalid = isvalid
结果是:
20.622 s 394.0 MB
所以 numba jitted 类不是(还?)一个好的选择。
推荐阅读
- ruby-on-rails - 如何获取图像变体的高度和宽度?
- postgresql - Docker 正在寻找另一个 postgres 参考
- r - 如何为另一列中的每个唯一值返回列的最大值?
- c++ - fastNlMeansDenoising 的 CUDA 版本与 CPU 版本不匹配?
- go - python“不返回”等价
- express - 使用 FeathersJS 看似限制性的 api 方法时如何组织端点?
- javascript - 如何在链接上使用 getElementById 来获取文本字段值
- reactjs - 同一自定义钩子的不同调用之间的数据泄漏(使用 useReducer)
- javascript - 嵌入一个外部小部件,但在加载正确的脚本之前它会以未定义的形式返回
- sql - 一对一映射 Spring boot