Python作为程序语言最被人诟病的一点在于其性能。Python作为动态语言,性能并不是它的优势,但很多时候实际也不会面临太多性能问题,所以Python的使用场景很广。但是,总会有碰到性能问题的一天,这种情况下首要的就是要在Python技术框架内来进行性能优化了。

决定是否优化

性能优化是手段,是为了解决其它一些问题。是否将性能优化作为正式开发任务,首要的就是达成共识。开发期的项目自然好说,上线后的项目就需要估计性能优化的工作量、相应风险以及评估大致的影响范围。让产品、开发、测试都意识到这项任务的必要性,而后才合适正式开始实施。

性能分析

性能优化必要步骤是分析瓶颈,首先得找到究竟慢在哪了,让后再对症下药去进行修改。Python性能分析,直接使用自带的cProfile进行处理就可以。用法也很简单,阅读官方文档中The Python Profilers
一节就足够了。

cProfile结果的可视化工具有不少,不过配合编码一起来的话,直接使用PyCharm打开最为方便。可以快速跳转每一个耗时函数对应的代码。

关注那些调用次数多的以及单次性能差的,那些大概率就是需要被优化的逻辑。

优化检查

在定位问题后就可以进行修改了。控制每次优化改动的范围,一次改动一处,然后再次Profile对比。优化的每一次改进都需要数据的支撑,这样能保证行进在正确的方向上,同时时间上也能有所掌控。

一些代码的优化并不一定好实施,因此这种渐进性的优化方式,便于降低项目整体开发风险,能够保证随时有可用的版本,且是经过了一定优化的版本。

持续监控

在进行了性能优化之后,就需要注意对性能指标进行监控了。这样能够及时的了解性能全貌,一旦再次需要进行优化,也能及早得知决策。

Python代码优化的常见方向

减少不必要的函数调用

很多时候没有必要去优化一个函数实现本身,优先考虑是否能够降低这个函数的调用次数,或是直接删除这个函数。

这是一种很常见的情况,一些功能层面已经不再需要的遗留代码占据了很多性能。如果一份代码多人经手,需求多变,反复迭代,那么这种代码是很有可能存在的。

搞清楚,然后删掉它们。

让运行时的动态计算静态化

部分运行逻辑可以先离线生成得到静态结果,运行时直接加载静态结果就可以了,这部分性能也是应该要回收的。

适当应用缓存

那些不能直接静态化的计算可能也可以缓存化。资源的创建销毁也可以根据情况判定是否应用缓存或对象池资源池一类实现方式,避免重复运算。

需要注意的是避免因为缓存带来的内存泄漏。

减少gc

Python通过引用计数与gc进行对象内存管理。对于引用计数无法处理的情况,Python会唤起gc进行处理,gc对性能有一定影响。因此代码实现的时候最好gc友好,减少代码中的引用环。

另外也可以通过设置gc参数来控制gc触发的几率,减少在高频计算时的gc触发,在空闲时刻手动触发gc。

修改明显错误的代码实现

很多性能问题都是那些非关键逻辑导致的,这些逻辑往往一眼看去就欠缺性能上的考虑,比如,

  • 不断通过for循环去进行查找
  • 错误的选择了容器,导致不断的遍历查找
  • 在property实现中含有复杂的耗时逻辑,让调用方忽视这一点
  • 在循环中添加了很多可以移到循环之外只运行一次的函数调用
  • 可以实现成事件触发的逻辑变成了轮询
  • 函数中存在大量重复调用

这类代码本就不应该存在,找出来改掉,并尽力保证未来此类代码不再进入代码库。

高频调用中改进一些基础用法

比如这里列出的一些常见用法的性能比较,在高频调用的时候就可以看出性能差异,选择更有效率的实现方式。

优化算法

部分逻辑可能可以从是实现方法层面进行优化,优化算法复杂度本身。

将部分逻辑改为原生代码实现

通过C/C++来替换掉部分逻辑也是Python优化的常见手段。有几种选择,

  • 裸写C/C++扩展
  • 使用Boost.Python实现C++扩展
  • 使用Cython
  • 使用PyPy

裸写C/C++需要处理繁琐的对象转化以及引用处理,一般会选择使用Boost.Python。使用起来方便很多。

采用Cython可以不去写C++代码,但是要写Cython脚本。根据情况,有选择使用。

PyPy相当于替换了运行时,在运行时不受掌控的情况下是不能采用的。服务端的话,可以考虑部分使用,通过进程隔离的方式解决一些依赖上的问题。

需要注意保留必要的Python实现,以便在遇到问题时可以快速切换回去。