虽然运行速度慢是 Python 与生俱来的特点,大多数时候我们用 Python 就意味着放弃对性能的追求。但是,就算是用纯 Python 完成同一个任务,老手写出来的代码可能会比菜鸟写的代码块几倍,甚至是几十倍(这里不考虑算法的因素,只考虑语言方面的因素)。很多时候,我们将自己的代码运行缓慢地原因归结于python本来就很慢,从而心安理得地放弃深入探究。

但是,事实真的是这样吗?面对python代码,你有分析下面这些问题吗:

程序运行的速度如何?
        程序运行时间的瓶颈在哪里?
        能否稍加改进以提高运行速度呢?
        为了更好了解python程序,我们需要一套工具,能够记录代码运行时间,生成一个性能分析报告,方便彻底了解代码,从而进行针对性的优化(本篇侧重于代码性能分析,不关注如何优化)。

        谁快谁慢

假设有一个字符串,想将里面的空格替换为字符‘-’,用python实现起来很简单,下面是四种方案:

def slowest_replace():
    replace_list = []
    for i, char in enumerate(orignal_str):
        c = char if char != " " else "-"
        replace_list.append(c)
    return "".join(replace_list)

def slow_replace():
    replace_str = ""
    for i, char in enumerate(orignal_str):
        c = char if char != " " else "-"
        replace_str += c
    return replace_str

def fast_replace():
    return "-".join(orignal_str.split())

def fastest_replace():
    return orignal_str.replace(" ", "-")
这四种方案的效率如何呢,哪种方案比较慢呢?这是一个问题!

        时间断点

最直接的想法是在开始 replace 函数之前记录时间,程序结束后再记录时间,计算时间差即为程序运行时间。python提供了模块 time,其中 time.clock() 在Unix/Linux下返回的是CPU时间(浮点数表示的秒数),Win下返回的是以秒为单位的真实时间(Wall-clock time)。

由于替换函数耗时可能非常短,所以这里考虑分别执行 100000次,然后查看不同函数的效率。我们的性能分析辅助函数如下:

def _time_analyze_(func):
    from time import clock
    start = clock()
    for i in range(exec_times):
        func()
    finish = clock()
    print "{:<20}{:10.6} s".format(func.__name__ + ":", finish - start)
        这样就可以了解上面程序的运行时间情况:

第一种方案耗时是第四种的 45 倍多,大跌眼镜了吧!同样是 python代码,完成一样的功能,耗时可以差这么多。

为了避免每次在程序开始、结束时插入时间断点,然后计算耗时,可以考虑实现一个上下文管理器,具体代码如下:

class Timer(object):
    def __init__(self, verbose=False):
        self.verbose = verbose

def __enter__(self):
        self.start = clock()
        return self

def __exit__(self, *args):
        self.end = clock()
        self.secs = self.end - self.start
        self.msecs = self.secs * 1000  # millisecs
        if self.verbose:
            print 'elapsed time: %f ms' % self.msecs
        使用时只需要将要测量时间的代码段放进 with 语句即可,具体的使用例子放在 gist 上。

        timeit

上面手工插断点的方法十分原始,用起来不是那么方便,即使用了上下文管理器实现起来还是略显笨重。还好 Python 提供了timeit模块,用来测试代码块的运行时间。它既提供了命令行接口,又能用于代码文件之中。

        命令行接口

命令行接口可以像下面这样使用:

$ python -m timeit -n 1000000 '"I like to reading.".replace(" ", "-")'
1000000 loops, best of 3: 0.253 usec per loop
$ python -m timeit -s 'orignal_str = "I like to reading."' '"-".join(orignal_str.split())'
1000000 loops, best of 3: 0.53 usec per loop
        具体参数使用可以用命令 python -m timeit -h 查看帮助。使用较多的是下面的选项:

-s S, –setup=S: 用来初始化statement中的变量,只运行一次;
-n N, –number=N: 执行statement的次数,默认会选择一个合适的数字;
-r N, –repeat=N: 重复测试的次数,默认为3;

        Python 接口

可以用下面的程序测试四种 replace函数的运行情况(完整的测试程序可以在 gist 上找到):

def _timeit_analyze_(func):
    from timeit import Timer
    t1 = Timer("%s()" % func.__name__, "from __main__ import %s" % func.__name__)
    print "{:<20}{:10.6} s".format(func.__name__ + ":", t1.timeit(exec_times))
        运行结果如下:

Python的timeit提供了 timeit.Timer() 类,类构造方法如下:

Timer(stmt='pass', setup='pass', timer=<timer function>)
其中:

stmt: 要计时的语句或者函数;
setup: 为stmt语句构建环境的导入语句;
timer: 基于平台的时间函数(timer function);
Timer()类有三个方法:

timeit(number=1000000): 返回stmt执行number次的秒数(float);
repeat(repeat=3, number=1000000): repeat为重复整个测试的次数,number为执行stmt的次数,返回以秒记录的每个测试循环的耗时列表;
print_exc(file=None): 打印stmt的跟踪信息。
此外,timeit 还提供了另外三个函数方便使用,参数和 Timer 差不多。

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000)
timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=3, number=1000000)
timeit.default_timer()
profile

以上方法适用于比较简单的场合,更复杂的情况下,可以用标准库里面的profile或者cProfile,它可以统计程序里每一个函数的运行时间,并且提供了可视化的报表。大多情况下,建议使用cProfile,它是profile的C实现,适用于运行时间长的程序。不过有的系统可能不支持cProfile,此时只好用profile。

可以用下面程序测试 timeit_profile() 函数运行时间分配情况。

import cProfile
from time_profile import *

cProfile.run("timeit_profile()")
这        样的输出可能会很长,很多时候我们感兴趣的可能只有耗时最多的几个函数,这个时候先将cProfile 的输出保存到诊断文件中,然后用 pstats 定制更加有好的输出(完整代码在 gist 上)。

cProfile.run("timeit_profile()", "timeit")
p = pstats.Stats('timeit')
p.sort_stats('time')
p.print_stats(6)

如果觉得 pstas 使用不方便,还可以使用一些图形化工具,比如 gprof2dot 来可视化分析 cProfile 的诊断结果。

        vprof

vprof 也是一个不错的可视化工具,可以用来分析 Python 程序运行时间情况。

        line_profiler

上面的测试最多统计到函数的执行时间,很多时候我们想知道函数里面每一行代码的执行效率,这时候就可以用到 line_profiler 了。

line_profiler 的使用特别简单,在需要监控的函数前面加上 @profile 装饰器。然后用它提供的 kernprof -l -v [source_code.py] 行进行诊断。下面是一个简单的测试程序 line_profile.py:

from time_profile import slow_replace, slowest_replace

for i in xrange(10000):
    slow_replace()
    slowest_replace()

输出每列的含义如下:

Line #: 行号
Hits: 当前行执行的次数.
Time: 当前行执行耗费的时间,单位为 “Timer unit:”
Per Hit: 平均执行一次耗费的时间.
% Time: 当前行执行时间占总时间的比例.
Line Contents: 当前行的代码
line_profiler 执行时间的估计不是特别精确,不过可以用来分析当前函数中哪些行是瓶颈。

对于Python语音性能的一些个人见解的更多相关文章

  1. [转] Python 代码性能优化技巧

    选择了脚本语言就要忍受其速度,这句话在某种程度上说明了 python 作为脚本的一个不足之处,那就是执行效率和性能不够理想,特别是在 performance 较差的机器上,因此有必要进行一定的代码优化 ...

  2. Python代码性能优化技巧

    摘要:代码优化能够让程序运行更快,可以提高程序的执行效率等,对于一名软件开发人员来说,如何优化代码,从哪里入手进行优化?这些都是他们十分关心的问题.本文着重讲了如何优化Python代码,看完一定会让你 ...

  3. Python 代码性能优化技巧(转)

    原文:Python 代码性能优化技巧 Python 代码优化常见技巧 代码优化能够让程序运行更快,它是在不改变程序运行结果的情况下使得程序的运行效率更高,根据 80/20 原则,实现程序的重构.优化. ...

  4. Python 代码性能优化技巧

    选择了脚本语言就要忍受其速度,这句话在某种程度上说明了 python 作为脚本的一个不足之处,那就是执行效率和性能不够理想,特别是在 performance 较差的机器上,因此有必要进行一定的代码优化 ...

  5. 7个提升Python程序性能的好习惯

    原文作者:爱coding,会编程的核电工程师. 个人博客地址:zhihu.com/people/zhong-yun-75-63 掌握一些技巧,可尽量提高Python程序性能,也可以避免不必要的资源浪费 ...

  6. 关于Python Profilers性能分析器

    1. 介绍性能分析器 作者:btchenguang profiler是一个程序,用来描述运行时的程序性能,并且从不同方面提供统计数据加以表述.Python中含有3个模块提供这样的功能,分别是cProf ...

  7. 七个可以提升python程序性能的好习惯,你知道吗?

    掌握一些技巧,可尽量提高Python程序性能,也可以避免不必要的资源浪费.今天就为大家带来七个可以提升python程序性能的好习惯,赶快来学习吧:. 1.使用局部变量 尽量使用局部变量代替全局变量:便 ...

  8. python——关于Python Profilers性能分析器

    1. 介绍性能分析器 profiler是一个程序,用来描述运行时的程序性能,并且从不同方面提供统计数据加以表述.Python中含有3个模块提供这样的功能,分别是cProfile, profile和ps ...

  9. Python脚本性能剖析

    ################### #Python脚本性能剖析 ################### cProfile/profile/hotshot用于统计Python脚本各部分运行频率和耗费 ...

随机推荐

  1. Java 操作Word书签(三):用文本、图片、表格替换书签

    本篇文章将继续介绍通过Java来操作Word书签的方法,即替换Word中已有书签,包括用新的文本.图片.表格等替换原有书签处的内容. 使用工具:Free Spire.Doc for Java (免费版 ...

  2. SpringMVC的工作流程?Mybatis和hibernate区别?

    SpringMVC的工作流程?1. 用户发送请求至前端控制器DispatcherServlet2. DispatcherServlet收到请求调用HandlerMapping处理器映射器.3. 处理器 ...

  3. bootstrap-table 常用总结-1

    两种表格工具,今天都用到了,一种是我前几篇写到过的jqgrid,(传送门)另一个就是bootstrap-table了.用过之后会发现,两种表格的方式大同小异,但是为什么这次要换成bootstrap-t ...

  4. 修改so库中的依赖名

    修改so库中的依赖名 在ArchLinuxArm上有一些针对aarch64, arm, armeabi-v7a等Android常用架构的so库可以下载,有时候可以省去很多编译时间,且都是编译optim ...

  5. CMake指南

    版权申明: 本文原创首发于以下网站,您可以自由转载,但必须加入完整的版权声明 博客园:https://www.cnblogs.com/MogooStudio/ csdn博客:https://blog. ...

  6. Mysql 事务及其原理

    Mysql 事务及其原理 什么是事务 什么是事务?事务是作为单个逻辑工作单元执行的一系列操作,通俗易懂的说就是一组原子性的 SQL 查询.Mysql 中事务的支持在存储引擎层,MyISAM 存储引擎不 ...

  7. 设计冲刺Design Sprint - 阅读记录

    改进团队流程: 审查了头脑风暴 - brain storming的成果,真正付诸实践并且获得成功的想法并不是来自大喊大叫的头脑风暴.而是来自静下心来的一次思考. 1. 搭建舞台 在开始设计冲刺之前,你 ...

  8. [Go]TCP服务中读写进行协程分离

    读写两部分进行一下分离,中间通过chan进行传递数据 ,这样可以方便的在write中进行一些业务处理 single/snet/tcpconn.go package snet import ( &quo ...

  9. 《Web Development with Go》JWT认证

    时间晚了,先来一版调通的JWT普通认证, 明天再弄一个通过中间件,及gorilla,negroni库的认证, 这样正规些, 但认证通过之后,如何对应权限? 由于jwt-go从2升到3,还有rsa 10 ...

  10. java之List接口(单列集合)

    List接口概述 查询API我们可知:java.util.List 接口继承自 Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了 List 接口的对 象称为List集合.在Li ...