[Python] cProfile 教學

程式語言:Python
Package:cProfile, pstats

官方文件

功能:分析程式效率
# 程式呼叫方法
import cProfile
import re

# cProfile.run(command, filename=None, sort=-1)
cProfile.run('re.compile("foo|bar")', filename="result.out", sort="cumulative")
# 命令行
# 文件內容是以二進制的方式保存的,用文本編輯器打開時會亂碼,可用 pstats 分析並輸出
python -m cProfile [-o output_file] [-s sort_order] myscript.py
         199 function calls (194 primitive calls) in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
        1    0.000    0.000    0.001    0.001 re.py:221(compile)
        1    0.000    0.000    0.001    0.001 re.py:277(_compile)
        1    0.000    0.000    0.000    0.000 sre_compile.py:227(_compile_charset)
        1    0.000    0.000    0.000    0.000 sre_compile.py:255(_optimize_charset)
        1    0.000    0.000    0.000    0.000 sre_compile.py:444(_compile_info)
        2    0.000    0.000    0.000    0.000 sre_compile.py:545(isstring)
        1    0.000    0.000    0.000    0.000 sre_compile.py:548(_code)
        1    0.000    0.000    0.000    0.000 sre_compile.py:563(compile)
      3/1    0.000    0.000    0.000    0.000 sre_compile.py:70(_compile)
輸出每列的具體解釋如下
  • ncalls
    • 表示函數調用的次數
    • 若為兩個數字表示有 recursive 像 3/1
      前者 3 表示總調用次數,後者 1 表示主要調用次數(不含本身調用)
  • tottime
    • 表示指定函數的總運行時間,除掉函數中調用子函數的運行時間
  • percall
    • (第一個percall)等於 tottime/ncalls
  • cumtime
    • 表示該函數及其所有子函數的調用運行的時間,即函數開始調用到返回的時間
  • percall
    • (第二個percall)即函數運行一次的平均時間,等於 cumtime/ncalls
  • filename:lineno(function)
    • 每個函數調用的具體信息,檔名:行數 (函數名)

cProfile

文件內容是以二進制的方式保存的
用文本編輯器打開時會亂碼,需用 pstats 分析並輸出
  • cProfile.run(command, filename=None, sort=-1)
    • command
      • pass 到 exec()
        可以直接輸入想分析的動作,例:'re.compile("foo|bar")
    • filename
      • 輸出的檔案名
    • sort
      • 排序的依據
      • 意義
        'calls'呼叫次數
        'ncalls'呼叫次數
        'cumulative'總調用時間
        'cumtime'總調用時間
        'file'檔名
        'filename'檔名
        'module'檔名
        'pcalls'平均調用時間,不含子函數
        'line'行數
        'name'function 名字
        'nfl'表示以 name/file/line 順序排列
        'stdname'同 nfl,但檔名一樣時 lines 3, 20, 40 順序將是 20, 3, 40
        'time'總調用時間,不含子函數
        'tottime'總調用時間,不含子函數
  • cProfile.runctx(command, globals, locals, filename=None)
    • 可自行輸入所需參數
    import cProfile
    import re
    
    cProfile.runctx('re.compile(text)', globals(), {'text':'foo|bar'})
    
  • class cProfile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)
    • 當需要比 cProfile.run() 更精準的操作時,才會用到
    import cProfile
    import re
    
    pr = cProfile.Profile()
    pr.enable()
    re.compile('foo|bar')
    pr.disable()
    pr.print_stats()
    
    • enable()
      • 開始分析
    • disable()
      • 停止分析
    • create_stats()
      • 停止分析且內部記錄其分析結果
    • print_stats(sort=-1)
      • 印出分析報表
    • dump_stats(filename)
      • 輸出分析結果於檔案
    • run(cmd)
      • 執行後回傳 cProfile.Profile object
      • pr.run("re.compile('foo|bar')").print_stats()
    • runctx(cmd, globals, locals)
      • 執行後回傳 cProfile.Profile object,但可輸入 globals 跟 locals 做為變數
      • pr.runctx("re.compile(text)", globals(), {'text':'foo|bar'}).print_stats()
    • runcall(func, *args, **kwargs)
      • 執行後回傳執行結果,但可輸入 *args 跟 **kwargs 做為參數
      import re
      import cProfile
      
      pr = cProfile.Profile()
      pr.runcall(re.compile, 'foo|bar')
      # re.compile('foo|bar')
      pr.print_stats()
      

分析工具 pstats

# 程式呼叫方法
import pstats
 
# 創建Stats對象
p = pstats.Stats("result.out")
 
# strip_dirs(): 去掉無關的路徑信息
# sort_stats(): 排序,支持的方式和上述的一致
# print_stats(): 打印分析結果,可以指定打印前幾行
 
# 和直接運行cProfile.run("test()")的結果是一樣的
p.strip_dirs().sort_stats(-1).print_stats()
 
# 按照函數名排序,只打印前3行函數的信息, 參數還可為小數,表示前百分之幾的函數信息 
p.strip_dirs().sort_stats("name").print_stats(3)
 
# 按照運行時間和函數名進行排序,只顯示前 50%
p.strip_dirs().sort_stats("cumulative", "name").print_stats(0.5)
 
# 如果想知道有哪些函數調用了sum_num,只顯示前 50%
p.print_callers(0.5, "sum_num")
 
# 查看test()函數中調用了哪些函數
p.print_callees("test")
# 命令行
python -m pstats result.out

打 help,顯示可用指令如下
Documented commands (type help <topic>):
========================================
EOF  add  callees  callers  help  quit  read  reverse  sort  stats  strip
  • class pstats.Stats(*filenames or profile, stream=sys.stdout)
    • 新舊版本的檔案不見得具有相容性
    import re
    import cProfile, pstats
    
    pr = cProfile.Profile()
    pr.runcall(re.compile, 'foo|bar')
    with open('output.txt', 'w') as stream:
        stats = pstats.Stats(pr, stream=stream)
        stats.print_stats()
    
    • 參數
      • *filenames or profile
        • 檔案 或 cProfile.Profile object
      • stream
        • 可以是 stream = open('path/to/output', 'w'),直接寫入檔案
    • 方法
      • strip_dirs()
        • 移除 filename:lineno(function) 中的路徑
      • add(*filenames)
        • 加入其他分析結果
      • dump_stats(filename)
        • 輸出分析報表至檔案
      • sort_stats(*keys)
        • 排序報表
        • 可以多個,如 sort_stats('name', 'file', 'line')
      • reverse_order() 
        • 報表序列反轉
      • print_stats(*restrictions)
        • 輸出分析報表
        • *restrictions
          • 限制幾筆 (int)
          • 限制前多少% (float) 需小於 1
        • print_stats(0.1, 'foo:')
          • 只輸出前 10%,filename 有 foo: 的
        • print_stats('foo:', 0.1)
          • 只輸出 filename 有 foo: 的前 10%
      • print_callers(*restrictions)
        • 輸出哪些函數調用過指定函數的資訊
        • p.print_callers('init', 10)
          • 前十筆 init 被哪些函數調用過的資訊
      • print_callees(*restrictions) 
        • 輸出指定函數調用過哪些函數的資訊
        • p.print_callees('init', 0.2)
          • 前 20% init 調用過哪些函數的資訊

參考

使用cProfile分析Python程序性能
profile, cProfile, and pstats – Performance analysis of Python programs.
使用cProfile分析Python程序性能

留言