多说也挺惨的,不能平静的死去,不知道哪里冒出一堆垃圾用户 ..
Python
慢是因为这货吃 CPU
,而且多线程还不能有效利用多核,然而这货不仅吃 CPU
还吃内存,非常贪婪。
我的前一篇文章里面提到过一个项目,一直都没留心,这货大概加载了 70M 的磁盘文件到内存中以 dict
的形式保存,内存使用立马就飙升了大几百兆 .. (⊙v⊙)
看看 Python
的对象到底有多吃内存(下面的代码都是基于 Python3.6
的,Python2.x
只会多不会少):
>>> import sys
>>> sys.getsizeof(1)
28 # bytes
>>> sys.getsizeof(1<<64) # long in Py2
36
>>> sys.getsizeof(1.1)
24
>>> sys.getsizeof('s')
50
>>> sys.getsizeof('ss')
51
>>> sys.getsizeof(b'b')
34
>>> sys.getsizeof(b'bb')
35
>>> from decimal import Decimal
>>> sys.getsizeof(Decimal(3.4))
104
对于容器类型的对象,我们得使用这段 代码 递归的计算内存大小,这里就直接复制过来了:
from __future__ import print_function
from sys import getsizeof, stderr
from itertools import chain
from collections import deque
try:
from reprlib import repr
except ImportError:
pass
def total_size(o, handlers={}, verbose=False):
""" Returns the approximate memory footprint an object and all of its contents.
Automatically finds the contents of the following builtin containers and
their subclasses: tuple, list, deque, dict, set and frozenset.
To search other containers, add handlers to iterate over their contents:
handlers = {SomeContainerClass: iter,
OtherContainerClass: OtherContainerClass.get_elements}
"""
dict_handler = lambda d: chain.from_iterable(d.items())
all_handlers = {
tuple: iter,
list: iter,
deque: iter,
dict: dict_handler,
set: iter,
frozenset: iter,
}
all_handlers.update(handlers) # user handlers take precedence
seen = set() # track which object id's have already been seen
default_size = getsizeof(0) # estimate sizeof object without __sizeof__
def sizeof(o):
if id(o) in seen: # do not double count the same object
return 0
seen.add(id(o))
s = getsizeof(o, default_size)
if verbose:
print(s, type(o), repr(o), file=stderr)
for typ, handler in all_handlers.items():
if isinstance(o, typ):
s += sum(map(sizeof, handler(o)))
break
return s
return sizeof(o)
##### Example call #####
if __name__ == '__main__':
d = dict(
a=1, b=2.5, c=1<<64,
d=(1, 2, 3), e=[4, 5, 6], f={7, 8, 9},
g=b'bytes', h='unicode'
)
print(total_size(d, verbose=True))
输出是这样的:
368 <class 'dict'> {'a': 1, 'b': 2.5, 'c': 18446744073709551616, 'd': (1, 2, 3), ...}
50 <class 'str'> 'a'
28 <class 'int'> 1
50 <class 'str'> 'b'
24 <class 'float'> 2.5
50 <class 'str'> 'c'
36 <class 'int'> 18446744073709551616
50 <class 'str'> 'd'
72 <class 'tuple'> (1, 2, 3)
28 <class 'int'> 2
28 <class 'int'> 3
50 <class 'str'> 'e'
88 <class 'list'> [4, 5, 6]
28 <class 'int'> 4
28 <class 'int'> 5
28 <class 'int'> 6
50 <class 'str'> 'f'
224 <class 'set'> {7, 8, 9}
28 <class 'int'> 8
28 <class 'int'> 9
28 <class 'int'> 7
50 <class 'str'> 'g'
38 <class 'bytes'> b'bytes'
50 <class 'str'> 'h'
56 <class 'str'> 'unicode'
1558
没看错,这个小小的 dict
就干掉了近 1.5K 的内存… 可以的,实力在这里大家都看得到。
当然 Python
对于小型对象会使用对象池的方式来优化内存的使用率,但是这只能应用于大量相同的小对象,而且这篇文章里面提到了:
CPython manages small objects (less than 256 bytes) in special pools on 8-byte boundaries. There are pools for 1-8 bytes, 9-16 bytes, and all the way to 249-256 bytes. When an object of size 10 is allocated, it is allocated from the 16-byte pool for objects 9-16 bytes in size. So, even though it contains only 10 bytes of data, it will cost 16 bytes of memory. If you allocate 1,000,000 objects of size 10, you actually use 16,000,000 bytes and not 10,000,000 bytes as you may assume. This 60% overhead is obviously not trivial.
所以,谨慎使用 Python
对象缓存过大的数据集,万能的 Google
告诉我们可以使用标准库 shelve
/ sqlite3.connect(':memory:')
,第三方工具 numpy
/ redis
以及优化过的数据结构 trie
等等来代替 dict-like
的数据集。
2017/4/14: 今天有时间尝试着优化了下文章开头提到的那个项目,借助 guppy
这个工具追踪了对象的内存使用率,确实是 dict
占用了较大的内存,由于 SDK
的属性,为了适配 Java
那边的文件存储格式并且使用尽量少的依赖和避免不必要的程序复杂性,替换掉 dict
也显得不那么必要,但是仔细看发现 unicode
占用较大的内存,实际上就是 json.load
加载的解析后的字符串都是 unicode
的,所以就直接把 unicode
都转成的了 bytes
,想要吐糟的是 Python
的 json
库对自定义 decode
的支持弱爆了,需要用时间复杂度来换灵活性,尝试 hack
内置的Decoder
,看了几个版本的 Python
源码太不划算就放弃了,加载的时候慢点但是减少了一百多兆的内存也值了,其实这些字符串大部分包含的是数字,转成数字会减少更多的内存,也有小几十兆,但是想想写文件的时候又得一次转型增加程序的复杂性也挺不划算的,暂时能优化的就这么多,毕竟没有其它对象占太多内存,毕竟线上机器性能也强劲 ..
BTW
这篇文章提供了一个案例教我们怎样去优化内存的使用率,主要使用了 Heapy 这个工具来定位吃内存较多的对象,通过干掉临时对象(del large_data
),使用__slots__
魔法,干掉 tuple
,使用 Cython
,将对象的方法变成函数等方式来优化内存使用率。
另外,objgraph 可以用来追查内存泄露相关的问题。