雖然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)使用