虽然运行速度慢是 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. Create a Report in Visual Studio 在Visual Studio中创建报表

    In this lesson, you will learn how to create reports in the integrated reporting system. This system ...

  2. JS PopupAlert

    JS PopupAlert 可以在 JavaScript 中创建三种消息框:警告框.确认框.提示框. 警告框 警告框经常用于确保用户可以得到某些信息. 当警告框出现后,用户需要点击确定按钮才能继续进行 ...

  3. Bug 29041775 : ORA-41401: Define character set () does not match database character set ()

    oracle版本12.2.0.1 Errors in file /u01/app/oracle/diag/rdbms/sibcyb1/CYB111/trace/CYB111_q003_166752.t ...

  4. ASCII码表收藏

    ASCII码表 ASCII码值 ESC键 VK_ESCAPE (27)回车键: VK_RETURN (13)TAB键: VK_TAB (9)Caps Lock键: VK_CAPITAL (20)Shi ...

  5. 【Golang基础】defer执行顺序

    defer 执行顺序类似栈的先入后出原则(FILO)     一个defer引发的小坑:打开文件,读取内容,删除文件   // 原始问题代码 func testFun(){ // 打开文件 file, ...

  6. 花了三个月终于把所有的 Python 库全部整理了!可以说很全面了

    库名称简介 Chardet字符编码探测器,可以自动检测文本.网页.xml的编码. colorama主要用来给文本添加各种颜色,并且非常简单易用. Prettytable主要用于在终端或浏览器端构建格式 ...

  7. [考试反思]1112csp-s模拟测试111:二重

    还是AK场.考前信心赛? 而且T3的部分分还放反了所有80的都其实只有50. 总算在AK场真正AK了一次... 手感好,整场考试很顺利.要不是因为T3是原题可能就没这么好看了. 20minT1,50m ...

  8. [译]Vulkan教程(08)逻辑设备和队列

    [译]Vulkan教程(08)逻辑设备和队列 Introduction 入门 After selecting a physical device to use we need to set up a  ...

  9. 使用VBA从工作表中读图片,以及给工作表中写文件

    因为工作的原因,需要用到VBA,碰到读图片和写图片: Sub Macro01() '从工作表中保存图片 Application.ScreenUpdating = False Dim pth, shp, ...

  10. el-dialog模态窗点击空白不消失

    通过查阅ElementUI的官方文档,可以发现Dialog对话框组件提供了一个close-on-click-modal属性来设置el-dialog模态窗点击空白不消失. <el-dialog : ...