Python 性能調試工具Line-profiler極簡使用筆記

雖然Python屢屢被人詬病速度問題,但是該用的還得用,速度問題只能靠代碼優化來解決了。Line-Profiler是一個代碼優化工具,利用line—profiler我們可以得到我們每一行代碼的運行總時間以及單次平均運行時間,以便我們對耗時最長的地方進行優化。

安裝:

pip install line_profiler

1、極簡模式

下面我們使用line-profiler查看一個簡單實例各行代碼時間都花在哪。

import random

def do_stuff():
    numbers = []
    for i in range(1000):
        numbers.append(random.randint(0,1000))
    s = sum(numbers)
    l = [numbers[i]/43 for i in range(len(numbers))]
    m = ['hello'+str(numbers[i]) for i in range(len(numbers))]
    return None
 

if __name__ == '__main__':
    from line_profiler import LineProfiler
    
    lp = LineProfiler()
    lp_wrapper = lp(do_stuff)
    lp_wrapper()
    lp.print_stats()

下面我們再命令行運行一下看看時間都去哪了:

$ root@cpu-k8ss-0 # python loader_test.py 
Timer unit: 1e-06 s

Total time: 0.0075 s
File: loader_test.py
Function: do_stuff at line 88

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    88                                           def do_stuff():
    89         1          1.0      1.0      0.0      numbers = []
    90      1001        697.0      0.7      9.3      for i in range(1000):
    91      1000       6093.0      6.1     81.2          numbers.append(random.randint(0,1000))
    92         1         10.0     10.0      0.1      s = sum(numbers)
    93         1        240.0    240.0      3.2      l = [numbers[i]/43 for i in range(len(numbers))]
    94         1        458.0    458.0      6.1      m = ['hello'+str(numbers[i]) for i in range(len(numbers))]
    95         1          1.0      1.0      0.0      return None

上面輸出中,可以看到我們測試的函數為do_stuff, 起始于位于腳本的第88行。運行程序共花費時間0.0075 s。

緊接著的各列含義如下:

  • Line #, 對應腳本中的代碼行數;
  • Hits,對應行共計運行次數;
  • Time,對應行運行總時間;
  • Per Hit,對應行平均每次運行時間;
  • % Time, 對應行運行時間占程序運行總時間的比例;
  • Line Contents,對應的每一行的代碼內容。

從上面可以看到我們的時間主要花在了第一個生成數據的for循環中,共計占了90.5%,我們可以使用列表推導式來對其進行優化。

$ root@cpu-k8ss-0 # python loader_test.py 
Timer unit: 1e-06 s

Total time: 0.00503 s
File: loader_test.py
Function: do_stuff at line 88

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    88                                           def do_stuff():
    89                                               # numbers = []
    90                                               # for i in range(1000):
    91                                               # numbers.append(random.randint(0,1000))
    92         1       4434.0   4434.0     88.2      numbers = [random.randint(1,100) for i in range(1000)]
    93         1          8.0      8.0      0.2      s = sum(numbers)
    94         1        194.0    194.0      3.9      l = [numbers[i]/43 for i in range(len(numbers))]
    95         1        393.0    393.0      7.8      m = ['hello'+str(numbers[i]) for i in range(len(numbers))]
    96         1          1.0      1.0      0.0      return None

可以看到使用列表推導式后,我們的總時間減少了,生成數據這一步占程序總運行時間比例也降低了。

給函數傳入參數

下面我們將numbers數據生成過程放到外面,以參數形式傳入到函數中:

def do_stuff(numbers):
    # numbers = []
    # for i in range(1000):
    # numbers.append(random.randint(0,1000))
    #numbers = [random.randint(1,100) for i in range(1000)]
    s = sum(numbers)
    l = [numbers[i]/43 for i in range(len(numbers))]
    m = ['hello'+str(numbers[i]) for i in range(len(numbers))]
    return None

if __name__ == '__main__':
    from line_profiler import LineProfiler
    lp = LineProfiler()
    lp_wrapper = lp(do_stuff)
    # 生成參數并傳入
    numbers = [random.randint(1,100) for i in range(100000)]
    lp_wrapper(numbers)
    lp.print_stats()

命令行運行:

$ root@cpu-k8ss-0 # python loader_test.py 
Timer unit: 1e-06 s

Total time: 0.084887 s
File: loader_test.py
Function: do_stuff at line 88

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    88                                           def do_stuff(numbers):
    89                                               # numbers = []
    90                                               # for i in range(1000):
    91                                               # numbers.append(random.randint(0,1000))
    92                                               #numbers = [random.randint(1,100) for i in range(1000)]
    93         1        788.0    788.0      0.9      s = sum(numbers)
    94         1      26504.0  26504.0     31.2      l = [numbers[i]/43 for i in range(len(numbers))]
    95         1      57593.0  57593.0     67.8      m = ['hello'+str(numbers[i]) for i in range(len(numbers))]
    96         1          2.0      2.0      0.0      return None

可以看到,現在程序運行的時間主要花在計算除數和連接字符串了。

顯示內層調用函數的運行時間

在上面的例子中,我們將所有代碼放在了一個函數中,只查看這個函數的代碼的運行時間。line-profiler支持查看調用的函數內部運行時間。

def generate_numbers(n):
    numbers = [random.randint(1,100) for i in range(1000)]
    return numbers

 
def do_stuff(n):
    numbers = generate_numbers(n)
    s = sum(numbers)
    l = [numbers[i]/43 for i in range(len(numbers))]
    m = ['hello'+str(numbers[i]) for i in range(len(numbers))]
    return None

if __name__ == '__main__':
    from line_profiler import LineProfiler
    lp = LineProfiler()
    lp_wrapper = lp(do_stuff)
    # 加入并顯示調用函數各行代碼用時
    lp.add_function(generate_numbers)
    # 生成參數并傳入
    n = 100000
    lp_wrapper(n)
    lp.print_stats()

命令行運行:

$ root@cpu-k8ss-0 # python loader_test.py 
Timer unit: 1e-06 s

Total time: 0.005112 s
File: loader_test.py
Function: generate_numbers at line 87

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    87                                           def generate_numbers(n):
    88         1       5111.0   5111.0    100.0      numbers = [random.randint(1,100) for i in range(1000)]
    89         1          1.0      1.0      0.0      return numbers

Total time: 0.005709 s
File: loader_test.py
Function: do_stuff at line 92

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    92                                           def do_stuff(n):
    93         1       5125.0   5125.0     89.8      numbers = generate_numbers(n)
    94         1          8.0      8.0      0.1      s = sum(numbers)
    95         1        189.0    189.0      3.3      l = [numbers[i]/43 for i in range(len(numbers))]
    96         1        387.0    387.0      6.8      m = ['hello'+str(numbers[i]) for i in range(len(numbers))]
    97         1          0.0      0.0      0.0      return None

在上面我們可以看到兩個函數各行代碼的運行時間,非常方便。

當然,line-profile還可以通過裝飾器@profiler方式統計我們想要優化的函數。然后在命令行中通過命令$ kernprof -l script_to_profile.py來進行運行。

參考:
官方說明
使用line_profiler查看api接口函數每行代碼執行時間
python 性能調試工具(line_profiler)使用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容