首页 > 解决方案 > 为什么protobuf的内存比python中的普通dict+list小?

问题描述

我在嵌套的字典/列表中有大量的原始类型结构。结构相当复杂,并不重要。

如果我用 python 的内置类型 ( dict/ list/ float/ int/ str) 表示它,它需要 1.1 GB,但如果我将它存储protobuf并加载到内存中,它会小得多。总共约 250 MB。

我想知道这怎么可能。与某些外部库相比,python 中的内置类型效率低吗?

编辑:结构是从 json 文件加载的。所以对象之间没有内部引用

标签: pythonjsonpython-3.xprotocol-buffers

解决方案


“简单”python 对象,例如intor float,比它们使用的 C 对象需要更多的内存protobuf

让我们以一个listPython 整数为例与整数数组进行比较,例如在array.array(ie array.array('i', ...)) 中。

分析array.array很简单:丢弃 -object 的一些开销,array.arrays每个元素只需要 4 个字节(C 整数的大小)。

对于整数列表,情况完全不同:

  • 该列表不包含整数对象本身,而是指向对象的指针(864位可执行文件的附加字节)
  • 即使是一个小的非零整数也至少需要28字节(参见import sys; sys.getsizeof(1)返回 28):引用计数需要 8 个字节,保存指向整数函数表的指针需要 8 个字节,整数值的大小需要 8 个字节( Python 的整数可以远大于 2^32),并且至少有 4 个字节来保存整数值本身。
  • 还有4.5 字节的内存管理开销

long long int这意味着与可能的 4 个字节(如果我们使用,即 64 位整数,则为 8 个字节)相比,每个 Python 整数的成本高达 40.5 个字节。

doubles与( ie )的数组相比,具有 Python 浮点数的列表的情况类似,array.array('d',...)每个元素只需要大约 8 个字节。但是对于列表,我们有:

  • 该列表不包含浮点对象本身,而是指向对象的指针(864 位可执行文件的附加字节)
  • 浮点对象需要24字节(参见import sys; sys.getsizeof(1.0)返回 24):引用计数需要 8 个字节,保存指向浮点函数表的指针需要 8 个字节,保存double-value 本身需要 8 个字节。
  • 因为 24 是 8 的倍数,所以内存管理的开销“仅”约为 0.5 个字节。

这意味着 Python 浮点对象为 32.5 字节,而 C-double 为 8 字节。

protobuf在内部使用与数据相同的表示,array.array因此需要更少的内存(如您所见,大约少 4-5 倍)。numpy.array是数据类型的另一个示例,它保存原始 C 值,因此比列表需要更少的内存。


如果不需要在字典中搜索,那么将键值对保存在列表中将比在字典中需要更少的内存,因为不必维护用于搜索的结构(这会增加一些内存成本) ) - 这也是导致protobuf-data 内存占用更小的另一件事。


回答你的另一个问题:没有内置模块适用于 Python- dict,什么array.array适用于 Python- list,所以我借此机会厚颜无耻地为我的库插入广告:cykhash.

集合和映射cykhash 需要不到 25%的 Python内存,但速度dict差不多。set


推荐阅读