python性能优化之函数执行时间分析

最近发现项目API请求比较慢,通过抓包发现主要是response时间太长,于是就开始进行优化工作。优化工作的关键一步是定位出问题的瓶颈,对于优化速度来说,从优化函数执行时间这个维度去切入是一个不错的选择。

本文侧重分析,不展开如何优化

利器

工欲善其事,必先利其器,我们需要一套方便高效的工具记录函数运行时间。说是一套工具,但对于一个简单项目或者日常开发来说,实现一个工具类足矣,由于实现比较简单,直接上代码:

from functools import wraps

import cProfile
from line_profiler import LineProfiler

import time


def func_time(f):
    """
    简单记录执行时间
    :param f:
    :return:
    """

    @wraps(f)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = f(*args, **kwargs)
        end = time.time()
        print f.__name__, 'took', end - start, 'seconds'
        return result

    return wrapper


def func_cprofile(f):
    """
    内建分析器
    """

    @wraps(f)
    def wrapper(*args, **kwargs):
        profile = cProfile.Profile()
        try:
            profile.enable()
            result = f(*args, **kwargs)
            profile.disable()
            return result
        finally:
            profile.print_stats(sort='time')

    return wrapper



try:
    from line_profiler import LineProfiler


    def func_line_time(follow=[]):
        """
        每行代码执行时间详细报告
        :param follow: 内部调用方法
        :return:
        """
        def decorate(func):
            @wraps(func)
            def profiled_func(*args, **kwargs):
                try:
                    profiler = LineProfiler()
                    profiler.add_function(func)
                    for f in follow:
                        profiler.add_function(f)
                    profiler.enable_by_count()
                    return func(*args, **kwargs)
                finally:
                    profiler.print_stats()

            return profiled_func

        return decorate

except ImportError:
    def func_line_time(follow=[]):
        "Helpful if you accidentally leave in production!"
        def decorate(func):
            @wraps(func)
            def nothing(*args, **kwargs):
                return func(*args, **kwargs)

            return nothing

        return decorate

原始代码可以参考gist

如下,实现了3个装饰器函数func_time,func_cprofile,func_line_time,分别对应

  1. 简单输出函数的执行时间
  2. 利用python自带的内置分析包cProfile 分析,它主要统计函数调用以及每个函数所占的cpu时间
  3. 利用line_profiler开源项目,它可以统计每行代码的执行次数和执行时间。

使用说明

我们以一个简单的循环例子来说明一下,

def test():
    for x in range(10000000):
        print x
  • func_time

关于func_time我觉得没什么好说的,就是简单输出下函数调用时间,这个在我们粗略统计函数执行时间的时候可以使用

如下:

@func_time
def test():
    for x in range(10000000):
        print x
# 输出
test took 6.10190296173 seconds
  • func_cprofile

cProfile是python内置包,基于lsprof的用C语言实现的扩展应用,运行开销比较合理,没有外部依赖,适合做快速的概要测试

@func_cprofile
def test():
 for x in range(10000000):
     print x
# 输出
      3 function calls in 6.249 seconds

Ordered by: internal time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    6.022    6.022    6.249    6.249 test.py:41(test)
     1    0.227    0.227    0.227    0.227 {range}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

输出说明:

单位为秒

  1. 第一行告诉我们一共有3个函数被调用。

    正常开发过程,第一行更多是输出类似194 function calls (189 primiive calls) in 0.249 seconds,(189 primiive calls)表示189个是原生(primitive)调用,表明这些调用不涉及递归

  2. ncalls表示函数的调用次数,如果这一列有两个数值,表示有递归调用,第一个是总调用次数,第二个是原生调用次数。

  3. tottime是函数内部消耗的总时间(不包括调用其他函数的时间)。

  4. percall是tottime除以ncalls,表示每次调用平均消耗时间。

  5. cumtime是之前所有子函数消耗时间的累积和。

  6. percall是cumtime除以原生调用的数量,表示该函数调用时,每个原生调用的平均消耗时间。

  7. filename:lineno(function)为被分析函数所在文件名、行号、函数名。

  • func_line_time

line_profiler可以生成非常直接和详细的报告,但它系统开销很大,会比实际执行时间慢一个数量级

@func_line_time()
def test():
    for x in range(10000000):
        print x
# 输出
Timer unit: 1e-06 s

Total time: 14.4183 s
File: /xx/test.py
Function: test at line 41

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    41                                           @func_line_time()
    42                                           def test():
    43  10000001    4031936.0      0.4     28.0      for x in range(10000000):
    44  10000000   10386347.0      1.0     72.0          print x

输出说明:

单位为微秒

  1. Total Time:测试代码的总运行时间

  2. Line:代码行号

  3. Hits:表示每行代码运行的次数

  4. Time:每行代码运行的总时间

  5. Per Hits:每行代码运行一次的时间

  6. % Time:每行代码运行时间的百分比

总结

日常开发中,可以使用func_time,func_cprofile做基本检查,定位大致问题,使用func_line_time进行更细致的深入检查。

注:func_line_time 还可以检查函数内部调用的函数执行时间,通过follow参数指定对应的内部调用的函数声明即可,该参数是个数组,也就是说可以检查多个内部调用的函数

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容