小探Python内存使用overhead

内存使用10倍以上,出乎预料

周五在公司时,继续处理推荐相关的code,做物品之间的相似度矩阵计算:通过<用户,物品>矩阵,可以计算出物品(item)两两之间的相似度,生成相似度矩阵,它是协同过滤的基础数据。对每个物品,仅保留与它最相近的120个物品,以及相似度。程序用C++写的,加上用手工优化的hashmap,多核心同时计算,跑满8个CPU,2分钟左右可以算完600万左右的item的相似度计算。

算完后,相似度关系存为binary的文件(为了快速load)。binary仅对机器友好,对人不友好。得想办法让人可以方便得查看。打算用tornado做一个web界面,可以随便点击查看,这样也给展示更详细得信息提供了可能性:物品以ID表示,如团购项目ID。web工具,可以通过查询数据库,显示团购项目的详细信息,如标题,价格,品类,销量,商家,地址等。这工具能方便我了解数据,培养sense,以及算法排查,领导查看也方便(相比于给领导一个binary文件,似乎给一个可以点击的URL靠谱一些)。

由于相似度文件1G多一点(binary),C++ load到内存后,RAM在1.5G内。知道python耗内存,但开发机有16G内存,按照Python比C++多耗10倍计算,程序也能跑下来。基于这个考虑,迅速coding。C++load时,耗时在2s内,python慢悠悠的,30s还没有见停。浏览了会儿Hacker News。感觉机器卡:机器在辛苦的swap,Python进程已耗掉了12G+内存。果断kill -9

Python耗内存,早有心理准备,但一个数量级的内存跑程序还困难,还是出乎了意料。

C++处理

相似度在文件和内存都是map的形式:

# item_id 以及和它相似的 items,以及相似度scoreitem_id => (item_id, score)+

(item_id, score)在C++里面是一个struct:

struct IdScore {    int id;    float score;};# 在内存中,耗掉8bytessizeof(IdScore) => 8

C++通过mmap文件,通过强制类型转化,把文件中bytes转成了IdScore array。简单迅速。

通过sys.getsizeof查看对象大小

Python没有那么方便,需要通过struct.unpack 成tuple。

相对与C和C++里面的sizeof, Python里可以则是sys.getsizeof

import syssys.getsizeof((1, 1.0))  # 72

有些出乎意料,tuple算是很节约的了,也需要72 字节,是C++的9倍。

print sys.getsizeof([1, 1.0]) # 88 list。 list比tuple "贵" 一些print sys.getsizeof(1) # 24 int。int不便宜。print sys.getsizeof("") # 37 empty string。空字符串也要37 bytes,不便宜。print sys.getsizeof("abc") # 40 string with 3 charsclass IdScore(object):    def __init__(self, id, score):        self.id = id        self.score = score# 64。貌似比tuple少一点。那是假象,每个IdScore对象还包含一个__dict__# getsizeof 算 __dict__8个字节(一指针),但__dict__是独享,需要加进去# 可以通过__slots__ 节约 __dict__开销print sys.getsizeof(IdScore(1, 1.0))

数字1耗掉了24字节,空字符串耗掉37字节。

翻阅cpython源码

Python 的 int 实际上是一个 struct PyIntObject ,intobject.h#L23

typedef struct {    PyObject_HEAD    long ob_ival;} PyIntObject;/* PyObject_HEAD 的定义,在 https://github.com/python/cpython/blob/2.7/Include/object.h#L65  *//* Define pointers to support a doubly-linked list of all live heap objects. */#define _PyObject_HEAD_EXTRA            \    struct _object *_ob_next;           \    struct _object *_ob_prev;

两个指针,一个long,难怪有24bytes。

Python的其它对象也有类似的struct结构(至少有PyObject_HEAD),不便宜。

后记

虽然Python多用了很多珍贵的内存,但开发迅速,能节约很多宝贵的时间(用少量的money换时间,早实践了。2年前为自己配的台式机,果断上i7 2600,16G。今年初入的Macbook Pro retina也是16G的配置,貌似是非定制化的顶配)

能快速prototype,验证想法是非常重要的。

一般情况下,程序的优化是可以通过重新设计数据结构达到的。这也不例外。想到了一个很简单的优化,让python也能用不到2G内存,处理这堆数据了:

    mmap 数据文件到内存为array 建立Index:map,存储 item_it => array的下标(和老版本不同,老版本是把数据 unpack 到内存了。 查询时,通过map查到下标,再通过struct.unpack 得到和它相似的其它 items,以及score
小探Python内存使用overhead

相关文章:

你感兴趣的文章:

标签云: