python性能優(yōu)化之函數(shù)執(zhí)行時(shí)間分析

最近發(fā)現(xiàn)項(xiàng)目API請求比較慢,通過抓包發(fā)現(xiàn)主要是response時(shí)間太長叭首,于是就開始進(jìn)行優(yōu)化工作徙垫。優(yōu)化工作的關(guān)鍵一步是定位出問題的瓶頸,對于優(yōu)化速度來說放棒,從優(yōu)化函數(shù)執(zhí)行時(shí)間這個(gè)維度去切入是一個(gè)不錯(cuò)的選擇姻报。

本文側(cè)重分析,不展開如何優(yōu)化

利器

工欲善其事间螟,必先利其器吴旋,我們需要一套方便高效的工具記錄函數(shù)運(yùn)行時(shí)間。說是一套工具厢破,但對于一個(gè)簡單項(xiàng)目或者日常開發(fā)來說荣瑟,實(shí)現(xiàn)一個(gè)工具類足矣,由于實(shí)現(xiàn)比較簡單摩泪,直接上代碼:

from functools import wraps

import cProfile
from line_profiler import LineProfiler

import time


def func_time(f):
    """
    簡單記錄執(zhí)行時(shí)間
    :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):
    """
    內(nèi)建分析器
    """

    @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=[]):
        """
        每行代碼執(zhí)行時(shí)間詳細(xì)報(bào)告
        :param follow: 內(nèi)部調(diào)用方法
        :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

如下笆焰,實(shí)現(xiàn)了3個(gè)裝飾器函數(shù)func_time,func_cprofile,func_line_time,分別對應(yīng)

  1. 簡單輸出函數(shù)的執(zhí)行時(shí)間
  2. 利用python自帶的內(nèi)置分析包cProfile 分析,它主要統(tǒng)計(jì)函數(shù)調(diào)用以及每個(gè)函數(shù)所占的cpu時(shí)間
  3. 利用line_profiler開源項(xiàng)目,它可以統(tǒng)計(jì)每行代碼的執(zhí)行次數(shù)和執(zhí)行時(shí)間见坑。

使用說明

我們以一個(gè)簡單的循環(huán)例子來說明一下,

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

關(guān)于func_time我覺得沒什么好說的嚷掠,就是簡單輸出下函數(shù)調(diào)用時(shí)間捏检,這個(gè)在我們粗略統(tǒng)計(jì)函數(shù)執(zhí)行時(shí)間的時(shí)候可以使用

如下:

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

cProfile是python內(nèi)置包,基于lsprof的用C語言實(shí)現(xiàn)的擴(kuò)展應(yīng)用不皆,運(yùn)行開銷比較合理贯城,沒有外部依賴,適合做快速的概要測試

@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個(gè)函數(shù)被調(diào)用霹娄。

    正常開發(fā)過程能犯,第一行更多是輸出類似194 function calls (189 primiive calls) in 0.249 seconds,(189 primiive calls)表示189個(gè)是原生(primitive)調(diào)用犬耻,表明這些調(diào)用不涉及遞歸

  2. ncalls表示函數(shù)的調(diào)用次數(shù)踩晶,如果這一列有兩個(gè)數(shù)值,表示有遞歸調(diào)用枕磁,第一個(gè)是總調(diào)用次數(shù)渡蜻,第二個(gè)是原生調(diào)用次數(shù)。

  3. tottime是函數(shù)內(nèi)部消耗的總時(shí)間(不包括調(diào)用其他函數(shù)的時(shí)間)透典。

  4. percall是tottime除以ncalls,表示每次調(diào)用平均消耗時(shí)間顿苇。

  5. cumtime是之前所有子函數(shù)消耗時(shí)間的累積和峭咒。

  6. percall是cumtime除以原生調(diào)用的數(shù)量,表示該函數(shù)調(diào)用時(shí)纪岁,每個(gè)原生調(diào)用的平均消耗時(shí)間凑队。

  7. filename:lineno(function)為被分析函數(shù)所在文件名、行號(hào)幔翰、函數(shù)名漩氨。

  • func_line_time

line_profiler可以生成非常直接和詳細(xì)的報(bào)告,但它系統(tǒng)開銷很大遗增,會(huì)比實(shí)際執(zhí)行時(shí)間慢一個(gè)數(shù)量級(jí)

@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:測試代碼的總運(yùn)行時(shí)間

  2. Line:代碼行號(hào)

  3. Hits:表示每行代碼運(yùn)行的次數(shù)

  4. Time:每行代碼運(yùn)行的總時(shí)間

  5. Per Hits:每行代碼運(yùn)行一次的時(shí)間

  6. % Time:每行代碼運(yùn)行時(shí)間的百分比

總結(jié)

日常開發(fā)中叫惊,可以使用func_time,func_cprofile做基本檢查,定位大致問題做修,使用func_line_time進(jìn)行更細(xì)致的深入檢查霍狰。

注:func_line_time 還可以檢查函數(shù)內(nèi)部調(diào)用的函數(shù)執(zhí)行時(shí)間,通過follow參數(shù)指定對應(yīng)的內(nèi)部調(diào)用的函數(shù)聲明即可饰及,該參數(shù)是個(gè)數(shù)組蔗坯,也就是說可以檢查多個(gè)內(nèi)部調(diào)用的函數(shù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市燎含,隨后出現(xiàn)的幾起案子宾濒,更是在濱河造成了極大的恐慌,老刑警劉巖屏箍,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绘梦,死亡現(xiàn)場離奇詭異橘忱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谚咬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門鹦付,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人择卦,你說我怎么就攤上這事敲长。” “怎么了秉继?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵祈噪,是天一觀的道長。 經(jīng)常有香客問我尚辑,道長辑鲤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任杠茬,我火速辦了婚禮月褥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瓢喉。我一直安慰自己宁赤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布栓票。 她就那樣靜靜地躺著决左,像睡著了一般。 火紅的嫁衣襯著肌膚如雪走贪。 梳的紋絲不亂的頭發(fā)上佛猛,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音坠狡,去河邊找鬼继找。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逃沿,可吹牛的內(nèi)容都是我干的码荔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼感挥,長吁一口氣:“原來是場噩夢啊……” “哼缩搅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起触幼,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤硼瓣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堂鲤,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亿傅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瘟栖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葵擎。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖半哟,靈堂內(nèi)的尸體忽然破棺而出酬滤,到底是詐尸還是另有隱情,我是刑警寧澤寓涨,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布盯串,位于F島的核電站,受9級(jí)特大地震影響戒良,放射性物質(zhì)發(fā)生泄漏体捏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一糯崎、第九天 我趴在偏房一處隱蔽的房頂上張望几缭。 院中可真熱鬧,春花似錦沃呢、人聲如沸年栓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽韵洋。三九已至竿刁,卻和暖如春黄锤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背食拜。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國打工鸵熟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人负甸。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓流强,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呻待。 傳聞我的和親對象是個(gè)殘疾皇子打月,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容