<strike id="ohtnr"></strike>
<tr id="ohtnr"><sup id="ohtnr"><mark id="ohtnr"></mark></sup></tr>
    <code id="ohtnr"><menu id="ohtnr"><sub id="ohtnr"></sub></menu></code>

  1. <code id="ohtnr"><small id="ohtnr"><track id="ohtnr"></track></small></code>
    <tr id="ohtnr"></tr>
  2. 技術干貨 > 文章詳情

    Python丨Python 性能分析大全

    3年前 Python

            雖然運行速度慢是 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 執行時間的估計不是特別精確,不過可以用來分析當前函數中哪些行是瓶頸。


    原文來自tuicool

    推薦閱讀:麥子專題丨1分鐘了解Python開發

                你必知的10個Python省時高招

                那些令人相見恨晚的Python小技巧

                Python教程丨初學者必看的5本入門書籍

    推薦學習:Python開發企業直通班帶你玩轉Python開發

    ——————

    版權保護聲明:本文僅代表作者觀點,麥子學院可能會進行刪節修改,但不代表麥子學院的官方立場。我們極其尊重并保護原創作品的版權,若原作者有任何疑問,請聯系微信號:chengxuyuan8。

    0

    登錄 后參與討論

    沒有更多評論了

    免費領取價值1888元求職寶典!

    客服熱線 400-862-8862

    回到頂部

    日本av电影-av电影-av在线-日本av-亚洲av-av视频-欧美av-av网站-