Python 应用剖析工具介绍

简介: 本文作者为来自 HumanGeo 的工程师 Davis,主要介绍了用于 Python 应用性能分析的几个工具。由国内 ITOM 管理平台 OneAPM 编译呈现。

【编者按】本文作者为来自 HumanGeo 的工程师 Davis,主要介绍了用于 Python 应用性能分析的几个工具。由国内 ITOM 管理平台 OneAPM 编译呈现。

HumanGeo,我们广泛使用 Python 进行编程,并且乐趣无穷。用 Python 写的程序不仅整洁美观,而且运行速度快得惊人。不论是私底下还是工作中,Python 都是笔者最爱的语言。然而,即便是 Python 这样美妙的语言,却也可能出现运行缓慢的情况。幸运的是,有许多不错的工具,可以帮助我们分析 Python 代码,从而保证其运行效率。

当笔者刚开始在 HumanGeo 工作时,就曾遇到过一个运行一次耗时数小时的程序,而笔者的任务,就是找出其性能瓶颈,再尽可能地提高其运行效率。当时,笔者使用了许多工具,包括 cProfile, [PyCallGraph]6,甚至 PyPy(一个运行快速的 Python 解释器),以确定最佳的程序优化方案。在本文中,笔者将介绍上述工具(为了保持生产环境中的解释器一致性,本文将不会介绍 PyPy 工具)的使用方法。甚至即便是最老练的开发者,也可以借助这些工具进一步优化他们的代码。

免责声明:不要过早地进行优化!有关过早优化的详细分析请查阅本文

工具

闲话少叙,下面开始介绍分析 Python 代码的几种便捷工具。

cProfile

CPython distribution 自带两种分析工具:profilecProfile。两者使用同样的 API,按理说运行效果应该差不多。然而,前者的运行时开销更大,因此,本文将主要介绍 cProfile

借助 cProfile,可以轻松实现对代码的深入分析,并且了解代码的哪些部分亟待提升。查看下面的缓慢代码实例:

--> % cat slow.py
import time

def main():    
  sum = 0    
  for i in range(10):        
      sum += expensive(i // 2)    
  return sum
  
def expensive(t):    
   time.sleep(t)    
   return t
   
if __name__ == '__main__':
    print(main())

在上面的代码中,笔者通过调用 time.sleep 方法,模拟一个运行时间很长的程序,并假定运行结果很重要。接下来,对这段代码进行分析,结果如下:

--> % python -m cProfile slow.py
20
         34 function calls in 20.030 seconds

   Ordered by: standard name   
 ncalls  tottime  percall  cumtime  percall filename:lineno(function)        
 1    0.000    0.000    0.000    0.000 __future__.py:48(<module>)        
 1    0.000    0.000    0.000    0.000 __future__.py:74(_Feature)        
 7    0.000    0.000    0.000    0.000 __future__.py:75(__init__)       
 10    0.000    0.000   20.027    2.003 slow.py:11(expensive)        
 1    0.002    0.002   20.030   20.030 slow.py:2(<module>)        
 1    0.000    0.000   20.027   20.027 slow.py:5(main)        
 1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}        
 1    0.000    0.000    0.000    0.000 {print}        
 1    0.000    0.000    0.000    0.000 {range}       
 10   20.027    2.003   20.027    2.003 {time.sleep}

我们发现,分析结果相当琐碎。其实,可以用更有益的方式组织分析结果。在上例中,调用列表是按照字母顺序排列的,这对我们并无价值。笔者更愿意看到按照调用次数或累计运行时间排列的调用情况。幸运的是,通过 -s 参数就能实现这一点。我们马上就能看到存在问题的代码段了!

--> % python -m cProfile -s calls slow.py
20
         34 function calls in 20.028 seconds

   Ordered by: call count   
   
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)       
   10    0.000    0.000   20.025    2.003 slow.py:11(expensive)       
   10   20.025    2.003   20.025    2.003 {time.sleep}        
   7    0.000    0.000    0.000    0.000 __future__.py:75(__init__)        
   1    0.000    0.000   20.026   20.026 slow.py:5(main)        
   1    0.000    0.000    0.000    0.000 __future__.py:74(_Feature)        
   1    0.000    0.000    0.000    0.000 {print}        
   1    0.000    0.000    0.000    0.000 __future__.py:48(<module>)        
   1    0.003    0.003   20.028   20.028 slow.py:2(<module>)        
   1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}        
   1    0.000    0.000    0.000    0.000 {range}

果然!我们发现,存在问题的代码就在 expensive 函数当中。该函数在执行结束之前调用了多次 time.sleep 方法,因此导致了程序的速度下降。

-s参数的有效取值列表可以在此 Python 文档中找到。如果你想将分析结果保存到一个文件中,记得使用输出选项 -o

基本功能介绍完毕之后,让我们来看看使用分析工具查找问题代码的其他方法。

PyCallGraph

PyCallGraph 可以看做是 cProfile 的可视化扩展工具。借助该工具,我们可以通过出色的 Graphviz 图片了解代码执行的路径。PyCallGraph 并未包含在标准的 Python 安装包内,因此,需要通过如下语句,进行简单的安装:

-> % pip install pycallgraph

通过下面的指令,就能运行图形化应用:

-> % pycallgraph graphviz -- python slow.py

运行完毕之后,在运行脚本的目录下会出现一张 pycallgraph.png 图片文件。同时,还应该得到相似的分析结果(如果你之前已经用 cProfile 分析过了)。结果中的数据应该与 cProfile 提供的结果一致。不过,PyCallGraph 的优点在于,它能展示被调用函数相互间的关系。

让我们来看看图片到底长什么样:

Python 应用剖析工具介绍

这多方便啊!图片显示了程序的运行路径,告诉我们程序经历过的每个函数、模块以及文件,还带有运行时间与调用次数等信息。如果在庞大的应用中运行该分析工具,会得到一张巨大的图片。但是,根据颜色的差别,我们仍能轻易找到存在问题的代码块。下面是 PyCallGraph 文档中提供的一张图片,展示了一段复杂的正则表达式调用中代码的运行路径:

Python 应用剖析工具介绍

点此获取此图分析的源码

这些信息有什么用?

一旦我们确定了导致问题代码的根源,就可以选择合适的解决方案优化代码,为其提速。下面,让我们根据特定的情况,探讨一些缓慢代码可行的解决方案。

I/O

如果你发现自己的代码严重依赖于输入/输出,譬如,需要发送很多 Web 请求,那么,Python 的标准线程模块或许就能帮你解决该问题。由于 CPython 的全局锁机制(Global Interpreter Lock,GIL)不允许为代码中心任务同时使用多个核,非 I/O 相关的线程并不适合用 Python 实现。

正则表达式

人们都说,一旦你决定用正则表达式解决某个问题,你就有两个问题要解决了。正则表达式真的很难用对,而且难以维护。关于这一点,笔者可以写一篇长篇大论进行阐述。(但是,我不会写的:)。正则表达式真的不简单,我相信有很多博文已经做了详尽的阐述。)不过,在此,笔者将介绍几个有用的技巧:

  1. 避免使用 .*,贪婪的匹配所有运算符运行起来非常慢,尽可能使用字符类才是更好的选择。
  2. 避免使用正则表达式!其实,许多正则表达式都可以用简单的字符串方法替代,比如 str.startswithstr.endswith
    方法。阅读 str 文档可以找到更多有用的信息。
  3. 多使用 re.VERBOSE!Python 的正则表达式引擎非常强大,超级有用,一定要好好利用!

以上是有关正则表达式笔者想说的全部内容。如果你想要更多信息,相信网络上还有很多好的文章。

Python 代码

以笔者之前剖析过的代码为例,我们的 Python 函数会运行成千上万次以找出英文词的词根。该函数最迷人的地方在于,其进行的操作很容易缓存。保存函数的运行结果之后,代码的运行速度提升了整整十倍。而在 Python 中创建缓存是轻而易举的事情:

from functools import wraps
def memoize(f):
    cache = {}    
    @wraps(f)    
    def inner(arg):       
       if arg not in cache:
            cache[arg] = f(arg)        
       return cache[arg]   
     return inner

该技术名为记忆(memoization),在具体实现时会执行为装饰器,可轻易应用在 Python 函数中,如下所示:

import time
@memoize
def slow(you):
    time.sleep(3)
    print("Hello after 3 seconds, {}!".format(you))    
    return 3

现在,如果我们多次运行该函数,运行结果就会立即出现:

>>> slow("Davis")
Hello after 3 seconds, Davis!
3
>>> slow("Davis")
3
>>> slow("Visitor")
Hello after 3 seconds, Visitor!
3
>>> slow("Visitor")
3

对于该项目来说,这是极大的速度提升。而且代码运行起来也没有出现故障。

免责声明:请确保该方法只用于 pure 函数!如果将记忆(memoization)用于带有副作用(譬如:I/O)的函数,缓存可能无法达到预期的效果。

其他情况

如果你的代码无法使用记忆(memoization)技巧,你的算法也不像 O(n!) 这样疯狂,或者代码的剖析结果也没有引人注意的地方,这可能说明你的代码并不存在显著的问题。这时候,你可以尝试一下别的运行环境或语言。PyPy 就是一个好的选择,你可能还要将算法用C语言扩展方法重写一下。幸运的是,笔者之前的项目并未走到这一步,但是这仍是很好的排错方案。

结论

剖析代码可以帮助你理解项目的执行流程、找出潜在的问题代码,以及作为开发者该如何提升程序运行速度。Python 剖析工具不但功能强大,简单易用,而且足够深入以快速找出问题根源。虽然 Python 并不是以快速著称的语言,但这并不意味着你的代码应该拖拖拉拉。管理好自己的算法,适时进行剖析,但绝不要过早优化!

本文转自 OneAPM 官方博客

原文地址:http://blog.thehumangeo.com/2015/07/28/profiling-in-python/

相关文章
|
12天前
|
存储 数据采集 人工智能
Python编程入门:从零基础到实战应用
本文是一篇面向初学者的Python编程教程,旨在帮助读者从零开始学习Python编程语言。文章首先介绍了Python的基本概念和特点,然后通过一个简单的例子展示了如何编写Python代码。接下来,文章详细介绍了Python的数据类型、变量、运算符、控制结构、函数等基本语法知识。最后,文章通过一个实战项目——制作一个简单的计算器程序,帮助读者巩固所学知识并提高编程技能。
|
23天前
|
人工智能 安全 Java
Java和Python在企业中的应用情况
Java和Python在企业中的应用情况
46 7
|
21天前
|
机器学习/深度学习 Python
堆叠集成策略的原理、实现方法及Python应用。堆叠通过多层模型组合,先用不同基础模型生成预测,再用元学习器整合这些预测,提升模型性能
本文深入探讨了堆叠集成策略的原理、实现方法及Python应用。堆叠通过多层模型组合,先用不同基础模型生成预测,再用元学习器整合这些预测,提升模型性能。文章详细介绍了堆叠的实现步骤,包括数据准备、基础模型训练、新训练集构建及元学习器训练,并讨论了其优缺点。
41 3
|
21天前
|
机器学习/深度学习 算法 数据挖掘
线性回归模型的原理、实现及应用,特别是在 Python 中的实践
本文深入探讨了线性回归模型的原理、实现及应用,特别是在 Python 中的实践。线性回归假设因变量与自变量间存在线性关系,通过建立线性方程预测未知数据。文章介绍了模型的基本原理、实现步骤、Python 常用库(如 Scikit-learn 和 Statsmodels)、参数解释、优缺点及扩展应用,强调了其在数据分析中的重要性和局限性。
45 3
|
26天前
|
存储 监控 安全
如何在Python Web开发中确保应用的安全性?
如何在Python Web开发中确保应用的安全性?
|
11天前
|
数据可视化 编译器 Python
Manim:数学可视化的强大工具 | python小知识
Manim(Manim Community Edition)是由3Blue1Brown的Grant Sanderson开发的数学动画引擎,专为数学和科学可视化设计。它结合了Python的灵活性与LaTeX的精确性,支持多领域的内容展示,能生成清晰、精确的数学动画,广泛应用于教育视频制作。安装简单,入门容易,适合教育工作者和编程爱好者使用。
67 7
|
21天前
|
存储 前端开发 API
Python在移动应用开发中的应用日益广泛
Python在移动应用开发中的应用日益广泛
38 10
|
15天前
|
缓存 开发者 Python
深入探索Python中的装饰器:原理、应用与最佳实践####
本文作为技术性深度解析文章,旨在揭开Python装饰器背后的神秘面纱,通过剖析其工作原理、多样化的应用场景及实践中的最佳策略,为中高级Python开发者提供一份详尽的指南。不同于常规摘要的概括性介绍,本文摘要将直接以一段精炼的代码示例开篇,随后简要阐述文章的核心价值与读者预期收获,引领读者快速进入装饰器的世界。 ```python # 示例:一个简单的日志记录装饰器 def log_decorator(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__} with args: {a
29 2
|
15天前
|
机器学习/深度学习 人工智能 自然语言处理
探索未来编程:Python在人工智能领域的深度应用与前景###
本文将深入探讨Python语言在人工智能(AI)领域的广泛应用,从基础原理到前沿实践,揭示其如何成为推动AI技术创新的关键力量。通过分析Python的简洁性、灵活性以及丰富的库支持,展现其在机器学习、深度学习、自然语言处理等子领域的卓越贡献,并展望Python在未来AI发展中的核心地位与潜在变革。 ###
|
21天前
|
机器学习/深度学习 自然语言处理 语音技术
Python在深度学习领域的应用,重点讲解了神经网络的基础概念、基本结构、训练过程及优化技巧
本文介绍了Python在深度学习领域的应用,重点讲解了神经网络的基础概念、基本结构、训练过程及优化技巧,并通过TensorFlow和PyTorch等库展示了实现神经网络的具体示例,涵盖图像识别、语音识别等多个应用场景。
45 8