最近事情好多,Blog好久没有更新了。今天上来写写最近解决的一个Python里的性能优化问题。
起因
之前为项目写过一个Sqlite数据库预处理的Python脚本,里面主要做了张新表,把其他表的数据填进去。当时主要考虑到维护性,条理清楚,就没太考虑Performance。之后QA发现模块的运行比原来慢了20倍,因为还是挺快,所以没有当时马上修正。
Profiling
这次Release要修掉这个问题。我的原则是,改进Performance一定要做Profiling,做到有的放矢才。
和Java的VirtualVM类似,Python 2.7也内置了几个Module做Profiling,我选择了cProfile。基本就是如下命令:
1 |
python -m cProfile -s tottime python_script |
“-s tottime”是让结果用总执行时间排序。
优化之前的执行结果,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
4012898 function calls (4011847 primitive calls) in 139.670 seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 281311 131.300 0.000 131.300 0.000 {method 'execute' of 'sqlite3.Cursor' objects} 121621 4.296 0.000 4.296 0.000 {method 'fetchall' of 'sqlite3.Cursor' objects} 121618 0.733 0.000 7.110 0.000 a_module.py:510(get_columns) 1 0.642 0.642 126.630 126.630 a_module.py:32(execute) 121616 0.470 0.000 7.580 0.000 a_module.py:495(has_field) 15202 0.440 0.000 7.500 0.000 a_module.py:406(update_parameters) 364921 0.365 0.000 0.365 0.000 {method 'format' of 'str' objects} 30405 0.254 0.000 0.254 0.000 {method 'fetchone' of 'sqlite3.Cursor' objects} 1 0.253 0.253 139.553 139.553 a_module.py:83(preprocess) 2322518 0.194 0.000 0.194 0.000 {method 'append' of 'list' objects} ... |
可见罪魁祸首就是sqlite3的Cursor的execute()方法,和原本猜测的也是一样的。优化的手法也很明确就是减少execute()的调用次数,使用batch和合并SQL语句的办法,很容易就用空间换回了时间。
优化之后的结果,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
1048606 function calls (1047555 primitive calls) in 11.653 seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 15235 10.101 0.001 10.101 0.001 {method 'execute' of 'sqlite3.Cursor' objects} 15202 0.309 0.000 1.448 0.000 a_module.py:422(update_a_parameters) 1 0.264 0.264 11.557 11.557 a_module.py:88(preprocess) 250907 0.230 0.000 0.230 0.000 {method 'format' of 'str' objects} 8 0.226 0.028 0.226 0.028 {method 'fetchall' of 'sqlite3.Cursor' objects} 7601 0.090 0.000 0.627 0.000 a_module.py:391(update_b_parameters) 121616 0.075 0.000 1.766 0.000 a_module.py:460(check_and_add_column_if_not_existed) 167222 0.067 0.000 0.067 0.000 {method 'keys' of 'sqlite3.Row' objects} ... |
优化之后只是原来的9%的Runtime。
总结
- 继续坚持用Profiler来做Performance的改进。
- 边改边用Profiler查看Performance有没有提升。
- 不要过分优化,否则代码没法看了。😀