在商业项目中使用了 Nuitka 来进行源码的编译和保护。结果用户反馈使用的 HTTP 请求好慢,数据量一大就超时了。
案发现场
将代码在本地运行,进行 strace 发现好慢。
0.000136 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 22), ...}) = 0 <0.000008> 0.000050 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5d83f8000 <0.000019> 0.000056 write(1, "\r\n", 2) = 2 <0.000017> 0.000048 write(1, "\r\n", 2) = 2 <0.000008> 0.000042 write(1, "\n", 1) = 1 <0.000007> 3.033367 brk(0x2a08000) = 0x2a08000 <0.000019> 1.743621 brk(0x2a41000) = 0x2a41000 <0.000019> 2.166131 brk(0x2a86000) = 0x2a86000 <0.000021> 2.687461 brk(0x2aaf000) = 0x2aaf000 <0.000017> 0.266587 brk(0x2ae3000) = 0x2ae3000 <0.000165> 4.485236 brk(0x2b3a000) = 0x2b3a000 <0.000129> 1.482029 mmap(NULL, 344064, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa596451000 <0.000331> 1.977664 mremap(0x7fa596451000, 344064, 389120, MREMAP_MAYMOVE) = 0x7fa5963f2000 <0.000284> 0.781643 munmap(0x7fa5963f2000, 389120) = 0 <0.000252> 0.053389 mmap(NULL, 1929216, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5962ce000 <0.000183>
一次请求开销 20 秒,发现 brk
之间的耗时有个 14 秒,加上下面的 mmap
,mremap
开销 2.8 秒。但是在纯 Python 文件下执行只需要 1 秒以内,非常的纳闷。
在使用 Nuitka 是将 Python 文件转化为 so 文件,而不是全部打成一个 so 文件,因此采用了 py 文件替换来进行调试。整个路径都用 py 文件来运行了,当然依赖的一些类库使用的 so 的形式,并没有什么进展。
没办法只能使用 pdb 来进行一行一行的跟踪了。临时写了一个文件做为入口。
python -m pdb test.py
进入后,使用
n 执行这行命令
s 进入函数内部
l 查看上下文的代码
一步一步的执行下后,发现在
class CJsonEncoder(json.JSONEncoder): """ to solve json.dumps datetime.datetime TypeError. TypeError: datetime.datetime(2014, 07, 12, 08, 56, 23) is not JSON serializable Use: json.dumps(data_obj, cls=CJsonEncoder) """ def default(self, obj): if isinstance(obj, datetime.datetime): return obj.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(obj, datetime.date): return obj.strftime('%Y-%m-%d') elif isinstance(obj, datetime.time): return obj.strftime('%H:%M:%S') else: return json.JSONEncoder.default(self, obj) ... result = { 'response':{ 'header':{ 'version':'1.0', 'returnCode':self._code, 'errorInfo':msg }, 'data':data }} result_str = json.dumps(result, cls=CJsonEncoder)
CJsonEncoder 耗时 4 秒
一个令人惊讶的数值,对比了数据, 一个是 json.dumps(result, cls=CJsonEncoder)
一个是 json.dumps(result)
,耗时分别是 20 秒和 16 秒,光在这个自定义的json解析上居然花费 4 秒的时间。不可思议。
简直无可忍受,感觉这个 Nuitka 解决方案不行呀。
使用 simplejson 类库替换
刚好最近一直在做 json 相关类库的工作,之前也是测试过 Python 自带的 json 类库,性能也确实差,可能和我的 Python 版本有关系,还在使用 Python 2.6 的版本,吐血。
现有的 simplejson,后来 Python 将 simplejson 纳入标准类库了,但是由于 Python 分发出去,就无法更新标准类库了, 但是 simplejson 可以不断的独立更新,所以维护的更好一些,很多的很多的程序也都是用它。
看到 http://stackoverflow.com/a/17823905 上有说 simplejson 看是否有加速过,可以用这个
import simplejson # If this is True, then c speedups are enabled. print bool(getattr(simplejson, '_speedups', False))
如果返回 True ,那么就有使用特殊的加速过。所以性能更好一些。
具体替换实现
考虑到 simplejson 是符合 Python 的标准类库的接口,所以可以无缝兼容在使用。在使用了 simplejson 后,这个耗时回到了 1 秒的时间。
def patch_json(): """ using simplejson replace json """ import json # noqa import simplejson # noqa sys.modules['json'] = sys.modules['simplejson']
在最开始的地方执行函数,替换掉标准的 json 的类库,让开发人员使用起来无感知,但是要注意做好兼容性测试。也要把这类隐形的替换在开发规范中声明好。
为什么使用了 Nuitka 变慢了
这个是我无法回答的了。因为 Nuitka 将 py 变成了 c++ 语言,再编译成 so 文件。按理不应该这么慢的。首先 strace 进入看,只有一些分配内存的情况系统调用。感觉这边在大量的一些 CPU 操作,不知道是不是 Nuitka 对这个优化不好,或者是老的 json 代码不行。
另外还发现,在 strace 中,一些查找 python 模块也还很耗时。
还有由于我的 Nuitka 解决方案是单个 py 直接变成单个 so,然后 py 和 so 混合使用的。不知道这边的切换会不会导致一些耗时的开销。
声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客
发表评论