分析python性能

python性能分析

阅读目录

回到顶部

调优简介

什么是性能分析

没有优化过的程序通常会在某些子程序(subroutine)上消耗大部分的CPU指令周期(CPU cycle)。性能分析就是分析代码和它正在使用的资源之间有着怎样的关系。

例如,性能分析可以告诉你一个指令占用了多少CPU时间,或者整个程序消耗了多少内存。

性能分析是通过使用一种被称为性能分析器(profiler)的工具,对程序或者二进制可执行文件(如果可以拿到)的源代码进行调整来完成的。

性能分析软件有两类方法论:基于事件的性能分析(event-based profiling)和统计式性能分析(statistical profiling)。

支持这类基于事件的性能分析的编程语言主要有以下几种。

Java:JVMTI(JVM Tools Interface,JVM工具接口)为性能分析器提供了钩子,可以跟踪诸如函数调用、线程相关的事件、类加载之类的事件。

.NET:和Java一样,.NET运行时提供了事件跟踪功能(https://en.wikibooks.org/wiki/Intro-duction_to_Software_Engineering/Testing/Profiling#Methods_of_data_gathering)。

Python: 开发者可以用 sys.setprofile 函数,跟踪 python_[call|return|exception]或 c_[call|return|exception] 之类的事件。

基于事件的性能分析器(event-based profiler,也称为轨迹性能分析器,tracing profiler)是通过收集程序执行过程中的具体事件进行工作的。

这些性能分析器会产生大量的数据。基本上,它们需要监听的事件越多,产生的数据量就越大。这导致它们不太实用,在开始对程序进行性能分析时也不是首选。

但是,当其他性能分析方法不够用或者不够精确时,它们可以作为最后的选择。

回到顶部

Python基于事件的性能分析器的简单示例代码

import sys

def profiler(frame, event, arg):
    print ‘PROFILER: %r %r‘ % (event, arg)

sys.setprofile(profiler)

#simple (and very ineficient) example of how to calculate the Fibonacci sequence for a number.
def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

def fib_seq(n):
    seq = [ ]
    if n > 0:
        seq.extend(fib_seq(n-1))
    seq.append(fib(n))
    return seq

print fib_seq(2)

执行结果:

 

统计式性能分析器以固定的时间间隔对程序计数器(program counter)进行抽样统计。这样做可以让开发者掌握目标程序在每个函数上消耗的时间。

由于它对程序计数器进行抽样,所以数据结果是对真实值的统计近似。不过,这类软件足以窥见被分析程序的性能细节,查出性能瓶颈之所在。

它使用抽样的方式(用操作系统中断),分析的数据更少,对性能造成的影响更小。

回到顶部

Linux统计式性能分析器OProfile(http://oprofile.sourceforge.net/news/)的分析结果:

Function name,File name,Times Encountered,Percentage
"func80000","statistical_profiling.c",30760,48.96%
"func40000","statistical_profiling.c",17515,27.88%
"func20000","static_functions.c",7141,11.37%
"func10000","static_functions.c",3572,5.69%
"func5000","static_functions.c",1787,2.84%
"func2000","static_functions.c",768,1.22%
func1500","statistical_profiling.c",701,1.12%
"func1000","static_functions.c",385,0.61%
"func500","statistical_profiling.c",194,0.31%

下面我们使用statprof进行分析:

import statprof
def profiler(frame, event, arg):
    print ‘PROFILER: %r %r‘ % (event, arg)

#simple (and very ineficient) example of how to calculate the Fibonacci sequence for a number.
def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

def fib_seq(n):
    seq = [ ]
    if n > 0:
        seq.extend(fib_seq(n-1))
    seq.append(fib(n))
    return seq

statprof.start()

try:
    print fib_seq(20)

finally:
    statprof.stop()
statprof.display()

执行结果:

$ python test.py
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
  %   cumulative      self
 time    seconds   seconds  name
100.00      0.01      0.01  test.py:15:fib
  0.00      0.01      0.00  test.py:21:fib_seq
  0.00      0.01      0.00  test.py:20:fib_seq
  0.00      0.01      0.00  test.py:27:<module>
---
Sample count: 2
Total time: 0.010000 seconds

注意上面代码我们把计算fib_seq的参数从2改成20,因为执行时间太快的情况下,statprof是获取不到任何信息的。

回到顶部

性能分析的重要性

  性能分析并不是每个程序都要做的事情,尤其对于那些小软件来说,是没多大必要的(不像那些杀手级嵌入式软件或专门用于演示的性能分析程序)。性能分析需要花时间,而且只有在程序中发现了错误的时候才有用。但是,仍然可以在此之前进行性能分析,捕获潜在的bug,这样可以节省后期的程序调试时间。

我们已经拥有测试驱动开发、代码审查、结对编程,以及其他让代码更加可靠且符合预期的手段,为什么还需要性能分析?

随着我们使用的编程语言越来越高级(几年间我们就从汇编语言进化到了JavaScript),我们愈加不关心CPU循环周期、内存配置、CPU寄存器等底层细节了。新一代程序员都通过高级语言学习编程技术,因为它们更容易理解而且开箱即用。但它们依然是对硬件和与硬件交互行为的抽象。随着这种趋势的增长,新的开发者越来越不会将性能分析作为软件开发中的一个步骤了。

如今,随便开发一个软件就可以获得上千用户。如果通过社交网络一推广,用户可能马上就会呈指数级增长。一旦用户量激增,程序通常会崩溃,或者变得异常缓慢,最终被客户无情抛弃。

上面这种情况,显然可能是由于糟糕的软件设计和缺乏扩展性的架构造成的。毕竟,一台服务器有限的内存和CPU资源也可能会成为软件的瓶颈。但是,另一种可能的原因,也是被证明过许多次的原因,就是我们的程序没有做过压力测试。我们没有考虑过资源消耗情况;我们只保证了测试已经通过,而且乐此不疲。

性能分析可以帮助我们避免项目崩溃夭折,因为它可以相当准确地为我们展示程序运行的情况,不论负载情况如何。因此,如果在负载非常低的情况下,通过性能分析发现软件在I/O操作上消耗了80%的时间,那么这就给了我们一个提示。是产品负载过重时,内存泄漏就可能发生。性能分析可以在负载真的过重之前,为我们提供足够的证据来发现这类隐患。

回到顶部

性能分析的内容

运行时间

如果你对运行的程序有一些经验(比如说你是一个网络开发者,正在使用一个网络框架),可能很清楚运行时间是不是太长。

例如,一个简单的网络服务器查询数据库、响应结果、反馈到客户端,一共需要100毫秒。但是,如果程序运行得很慢,做同样的事情需要花费60秒,你就得考虑做性能分析了。

import datetime

tstart = None
tend = None

def start_time():
    global tstart
    tstart = datetime.datetime.now()

def get_delta():
    global tstart
    tend = datetime.datetime.now()
    return tend - tstart

def fib(n):
    return n if n == 0 or n == 1 else fib(n-1) + fib(n-2)

def fib_seq(n):
    seq = [ ]
    if n > 0:
        seq.extend(fib_seq(n-1))
    seq.append(fib(n))
    return seq

start_time()
print "About to calculate the fibonacci sequence for the number 30"
delta1 = get_delta()

start_time()
seq = fib_seq(30)
delta2 = get_delta()

print "Now we print the numbers: "
start_time()
for n in seq:
    print n
delta3 = get_delta()

print "====== Profiling results ======="
print "Time required to print a simple message: %(delta1)s" % locals()
print "Time required to calculate fibonacci: %(delta2)s" % locals()
print "Time required to iterate and print the numbers: %(delta3)s" %locals()
print "====== ======="

执行结果:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

$ python test.py

About to calculate the fibonacci sequence for the number 30

Now we print the numbers:

0

1

1

2

3

5

8

13

21

34

55

89

144

233

377

610

987

1597

2584

4181

6765

10946

17711

28657

46368

75025

121393

196418

317811

514229

832040

====== Profiling results =======

Time required to print a simple message: 0:00:00.000064

Time required to calculate fibonacci: 0:00:01.430740

Time required to iterate and print the numbers: 0:00:00.000075

====== =======

可见计算部分是最消耗时间的。

发现瓶颈

只要你测量出了程序的运行时间,就可以把注意力移到运行慢的环节上做性能分析。一般瓶颈由下面的一种或者几种原因组成:

* 重的I/O操作,比如读取和分析大文件,长时间执行数据库查询,调用外部服务(比如HTTP请求),等等。

* 现了内存泄漏,消耗了所有的内存,导致后面的程序没有内存来正常执行。

* 未经优化的代码频繁执行。

* 可以缓存时密集的操作没有缓存,占用了大量资源。

I/O关联的代码(文件读/写、数据库查询等)很难优化,因为优化有可能会改变程序执行I/O操作的方式(通常是语言的核心函数操作I/O)。相反,优化计算关联的代码(比如程序使用的算法很糟糕),改善性能会比较容易(并不一定很简单)。这是因为优化计算关联的代码就是改写程序。

回到顶部

内存消耗和内存泄漏

内存消耗不仅仅是关注程序使用了多少内存,还应该考虑控制程序使用内存的数量。跟踪程序内存的消耗情况比较简单。最基本的方法就是使用操作系统的任务管理器。

它会显示很多信息,包括程序占用的内存数量或者占用总内存的百分比。任务管理器也是检查CPU时间使用情况的好工具。

在下面的top截图中,你会发现一个简单的Python程序(就是前面那段程序)几乎占用了全部CPU(99.8%),内存只用了0.1%。

当运行过程启动之后,内存消耗会在一个范围内不断增加。如果发现增幅超出范围,而且消

耗增大之后一直没有回落,就可以判断出现内存泄漏了。

回到顶部

过早优化的风险

优化通常被认为是一个好习惯。但是,如果一味优化反而违背了软件的设计原则就不好了。在开始开发一个新软件时,开发者经常犯的错误就是过早优化(permature optimization)。如果过早优化代码,结果可能会和原来的代码截然不同。它可能只是完整解决方案的一部分,还可能包含因优化驱动的设计决策而导致的错误。

一条经验法则是,如果你还没有对代码做过测量(性能分析)

优化往往不是个好主意。首先,应该集中精力完成代码,然后通过性能分析发现真正的性能瓶颈,最后对代码进行优化。

回到顶部

运行时间复杂度

运行时间复杂度(Running Time Complexity,RTC)用来对算法的运行时间进行量化。它是对算法在一定数量输入条件下的运行时间进行数学近似的结果。因为是数学近似,所以我们可以用这些数值对算法进行分类。

RTC常用的表示方法是大O标记(big O notation)。数学上,大O标记用于表示包含无限项的

函数的有限特征(类似于泰勒展开式)。如果把这个概念用于计算机科学,就可以把算法的运行

时间描述成渐进的有限特征(数量级)。

主要模型有:

常数时间——O(1):比如判断一个数是奇数还是偶数、用标准输出方式打印信息等。对于理论上更复杂的操作,比如在字典(或哈希表)中查找一个键的值,如果算法合理,就
        可以在常数时间内完成。技术上看,在哈希表中查找元素的消耗时间是O(1)平均时间,这意味着每次操作的平均时间(不考虑特殊情况)是固定值O(1)。
线性时间——O(n):比如查找无序列表中的最小元素、比较两个字符串、删除链表中的最后一项
对数时间——O(logn):对数时间(logarithmic time)复杂度的算法,表示随着输入数量的增加,算法的运行时间会达到固定的上限。随着输入数量的增加,对数函数开始增长很快,然后慢慢减速。
          它不会停止增长,但是越往后增长的速度越慢,甚至可以忽略不计。比如:二分查找(binary search)、计算斐波那契数列(用矩阵乘法)。
线性对数时间——O(nlogn):把前面两种时间类型组合起来就变成了线性对数时间(linearithmic time)。随着x的增大,算法的运行时间会快速增长。
            比如归并排序(merge sort)、堆排序(heap sort)、快速排序(quick sort,至少是平均运行时间)
阶乘时间——O(n!):阶乘时间(factorial time)复杂度的算法是最差的算法。其时间增速特别快,图都很难画。比如:用暴力破解搜索方法解货郎担问题(遍历所有可能的路径)。
平方时间——O(n 2 ):平方时间是另一个快速增长的时间复杂度。输入数量越多,需要消耗的时间越长(大多数算法都是这样,这类算法尤其如此)。
          平方时间复杂度的运行效率比线性时间复杂度要慢。比如冒泡排序(bubble sort)、遍历二维数组、插入排序(insertion sort)

速度:对数>线性>线性对数>平方>阶乘, 要考虑最好情况、正常情况和最差情况。

回到顶部

性能分析最佳实践

建立回归测试套件、思考代码结构、耐心、尽可能多地收集数据(其他数据资源,如网络应用的系统日志、自定义日志、系统资源快照(如操作系统任务管理器))、数据预处理、数据可视化

python中最出名的性能分析库:cProfile、line_profiler。

前者是标准库:https://docs.python.org/2/library/profile.html#module-cProfile。

后者参见:https://github.com/rkern/line_profiler。

专注于CPU时间。

声明:原文链接:my.oschina.net/u/1433482/blog/709219

时间: 2024-10-25 18:40:04

分析python性能的相关文章

Python性能分析指南

http://www.admin10000.com/document/2861.html 尽管并非每个你写的Python程序都需要严格的性能分析,但了解一下Python的生态系统中很多优秀的在你需要做性能分析的时候可以使用的工具仍然是一件值得去做的事. 分析一个程序的性能,最终都归结为回答4个基本的问题: 程序运行速度有多快? 运行速度瓶颈在哪儿? 程序使用了多少内存? 内存泄露发生在哪里? 下面,我们将使用一些优秀的工具深入回答这些问题. 使用time工具粗糙定时 首先,我们可以使用快速然而粗

Python性能分析指南(未完成)

英文原文:http://www.huyng.com/posts/python-performance-analysis/ 译文:http://www.oschina.net/translate/python-performance-analysis 虽然你所写的每个Python程序并不总是需要严密的性能分析,但是当这样的问题出现时,如果能知道Python生态系统中的许多种工具,这样总是可以让人安心的. 分析一个程序的性能可以归结为回答4个基本的问题: 1.它运行的有多块? 2.那里是速度的瓶颈?

Python 性能分析大全

虽然运行速度慢是 Python 与生俱来的特点,大多数时候我们用 Python 就意味着放弃对性能的追求.但是,就算是用纯 Python 完成同一个任务,老手写出来的代码可能会比菜鸟写的代码块几倍,甚至是几十倍(这里不考虑算法的因素,只考虑语言方面的因素).很多时候,我们将自己的代码运行缓慢地原因归结于python本来就很慢,从而心安理得地放弃深入探究. 但是,事实真的是这样吗?面对python代码,你有分析下面这些问题吗: 程序运行的速度如何?         程序运行时间的瓶颈在哪里?   

Python性能分析

Python性能分析 https://www.cnblogs.com/lrysjtu/p/5651816.html https://www.cnblogs.com/cbscan/articles/3341231.html 使用ipdb 使用profile import profile def profileTest(): Total =1; for i in range(10): Total=Total*(i+1) print Total return Total if __name__ ==

Python性能分析工具Profile

Python性能分析工具Profile 代码优化的前提是需要了解性能瓶颈在什么地方,程序运行的主要时间是消耗在哪里,对于比较复杂的代码可以借助一些工具来定位,python 内置了丰富的性能分析工具,如 profile,cProfile 与 hotshot 等.其中 Profiler 是 python 自带的一组程序,能够描述程序运行时候的性能,并提供各种统计帮助用户定位程序的性能瓶颈.Python 标准模块提供三种 profilers:cProfile,profile 以及 hotshot. p

Python性能鸡汤(转)

英文原文:http://blog.monitis.com/index.php/2012/02/13/python-performance-tips-part-1/ 英文原文:http://blog.monitis.com/index.php/2012/03/21/python-performance-tips-part-2/ 译文:http://www.oschina.net/question/1579_45822 Python是解释型语言,因此它的执行效率不高 [1] ,但这并不影响它的流行.

Python性能(转)

第一部分 阅读 Zen of Python,在Python解析器中输入 import this. 一个犀利的Python新手可能会注意到"解析"一词, 认为Python不过是另一门脚本语言. "它肯定很慢!" 毫无疑问:Python程序没有编译型语言高效快速. 甚至Python拥护者们会告诉你Python不适合这些领域. 然而,YouTube已用Python服务于每小时4千万视频的请求. 你所要做的就是编写高效的代码和需要时使用外部实现(C/C++)代码. 这里有一

Python性能鸡汤

第一部分 阅读 Zen of Python,在Python解析器中输入 import this. 一个犀利的Python新手可能会注意到"解析"一词, 认为Python不过是另一门脚本语言. "它肯定很慢!" 毫无疑问:Python程序没有编译型语言高效快速. 甚至Python拥护者们会告诉你Python不适合这些领域. 然而,YouTube已用Python服务于每小时4千万视频的请求. 你所要做的就是编写高效的代码和需要时使用外部实现(C/C++)代码. 这里有一

Python性能优化(转)

分成两部分:代码优化和工具优化 原文:http://my.oschina.net/xianggao/blog/102600 阅读 Zen of Python,在Python解析器中输入 import this. 一个犀利的Python新手可能会注意到"解析"一词, 认为Python不过是另一门脚本语言. "它肯定很慢!" 毫无疑问:Python程序没有编译型语言高效快速. 甚至Python拥护者们会告诉你Python不适合这些领域. 然而,YouTube已用Pyth