自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Python 性能分析大全

開發(fā)
為了更好了解python程序,我們需要一套工具,能夠記錄代碼運(yùn)行時(shí)間,生成一個(gè)性能分析報(bào)告,方便徹底了解代碼,從而進(jìn)行針對(duì)性的優(yōu)化(本篇側(cè)重于代碼性能分析,不關(guān)注如何優(yōu)化)。

 

 雖然運(yùn)行速度慢是 Python 與生俱來的特點(diǎn),大多數(shù)時(shí)候我們用 Python 就意味著放棄對(duì)性能的追求。但是,就算是用純 Python 完成同一個(gè)任務(wù),老手寫出來的代碼可能會(huì)比菜鳥寫的代碼塊幾倍,甚至是幾十倍(這里不考慮算法的因素,只考慮語言方面的因素)。很多時(shí)候,我們將自己的代碼運(yùn)行緩慢地原因歸結(jié)于python本來就很慢,從而心安理得地放棄深入探究。

但是,事實(shí)真的是這樣嗎?面對(duì)python代碼,你有分析下面這些問題嗎:

程序運(yùn)行的速度如何?

程序運(yùn)行時(shí)間的瓶頸在哪里?

能否稍加改進(jìn)以提高運(yùn)行速度呢?

為了更好了解python程序,我們需要一套工具,能夠記錄代碼運(yùn)行時(shí)間,生成一個(gè)性能分析報(bào)告,方便徹底了解代碼,從而進(jìn)行針對(duì)性的優(yōu)化(本篇側(cè)重于代碼性能分析,不關(guān)注如何優(yōu)化)。

誰快誰慢

假設(shè)有一個(gè)字符串,想將里面的空格替換為字符‘-’,用python實(shí)現(xiàn)起來很簡單,下面是四種方案:

  1. def slowest_replace(): 
  2.  
  3. replace_list = [] 
  4.  
  5. for i, char in enumerate(orignal_str): 
  6.  
  7. c = char if char != " " else "-" 
  8.  
  9. replace_list.append(c) 
  10.  
  11. return "".join(replace_list) 
  12.  
  13. def slow_replace(): 
  14.  
  15. replace_str = "" 
  16.  
  17. for i, char in enumerate(orignal_str): 
  18.  
  19. c = char if char != " " else "-" 
  20.  
  21. replace_str += c 
  22.  
  23. return replace_str 
  24.  
  25. def fast_replace(): 
  26.  
  27. return "-".join(orignal_str.split()) 
  28.  
  29. def fastest_replace(): 
  30.  
  31. return orignal_str.replace(" ""-"

這四種方案的效率如何呢,哪種方案比較慢呢?這是一個(gè)問題!

時(shí)間斷點(diǎn)

最直接的想法是在開始 replace 函數(shù)之前記錄時(shí)間,程序結(jié)束后再記錄時(shí)間,計(jì)算時(shí)間差即為程序運(yùn)行時(shí)間。python提供了模塊 time,其中 time.clock() 在Unix/Linux下返回的是CPU時(shí)間(浮點(diǎn)數(shù)表示的秒數(shù)),Win下返回的是以秒為單位的真實(shí)時(shí)間(Wall-clock time)。

由于替換函數(shù)耗時(shí)可能非常短,所以這里考慮分別執(zhí)行 100000次,然后查看不同函數(shù)的效率。我們的性能分析輔助函數(shù)如下:

  1. def _time_analyze_(func): 
  2.  
  3. from time import clock 
  4.  
  5. start = clock() 
  6.  
  7. for i in range(exec_times): 
  8.  
  9. func() 
  10.  
  11. finish = clock() 
  12.  
  13. print "{:<20}{:10.6} s".format(func.__name__ + ":", finish - start) 

這樣就可以了解上面程序的運(yùn)行時(shí)間情況:

 

 

***種方案耗時(shí)是第四種的 45 倍多,大跌眼鏡了吧!同樣是 python代碼,完成一樣的功能,耗時(shí)可以差這么多。

為了避免每次在程序開始、結(jié)束時(shí)插入時(shí)間斷點(diǎn),然后計(jì)算耗時(shí),可以考慮實(shí)現(xiàn)一個(gè)上下文管理器,具體代碼如下:

  1. class Timer(object): 
  2.  
  3. def __init__(self, verbose=False): 
  4.  
  5. self.verbose = verbose 
  6.  
  7. def __enter__(self): 
  8.  
  9. self.start = clock() 
  10.  
  11. return self 
  12.  
  13. def __exit__(self, *args): 
  14.  
  15. self.end = clock() 
  16.  
  17. self.secs = self.end - self.start 
  18.  
  19. self.msecs = self.secs * 1000 # millisecs 
  20.  
  21. if self.verbose: 
  22.  
  23. print 'elapsed time: %f ms' % self.msecs 

使用時(shí)只需要將要測(cè)量時(shí)間的代碼段放進(jìn) with 語句即可,具體的使用例子放在 gist上。

timeit

上面手工插斷點(diǎn)的方法十分原始,用起來不是那么方便,即使用了上下文管理器實(shí)現(xiàn)起來還是略顯笨重。還好 Python 提供了timeit模塊,用來測(cè)試代碼塊的運(yùn)行時(shí)間。它既提供了命令行接口,又能用于代碼文件之中。

命令行接口

命令行接口可以像下面這樣使用:

  1. $ python -m timeit -n 1000000 '"I like to reading.".replace(" ", "-")' 
  2.  
  3. 1000000 loops, best of 3: 0.253 usec per loop 
  4.  
  5. $ python -m timeit -s 'orignal_str = "I like to reading."' '"-".join(orignal_str.split())' 
  6.  
  7. 1000000 loops, best of 3: 0.53 usec per loop 

具體參數(shù)使用可以用命令 python -m timeit -h 查看幫助。使用較多的是下面的選項(xiàng):

-s S, –setup=S: 用來初始化statement中的變量,只運(yùn)行一次;

-n N, –number=N: 執(zhí)行statement的次數(shù),默認(rèn)會(huì)選擇一個(gè)合適的數(shù)字;

-r N, –repeat=N: 重復(fù)測(cè)試的次數(shù),默認(rèn)為3;

Python 接口

可以用下面的程序測(cè)試四種 replace函數(shù)的運(yùn)行情況(完整的測(cè)試程序可以在 gist 上找到):

  1. def _timeit_analyze_(func): 
  2.  
  3. from timeit import Timer 
  4.  
  5. t1 = Timer("%s()" % func.__name__, "from __main__ import %s" % func.__name__) 
  6.  
  7. print "{:<20}{:10.6} s".format(func.__name__ + ":", t1.timeit(exec_times)) 

運(yùn)行結(jié)果如下:

 

 

Python的timeit提供了 timeit.Timer() 類,類構(gòu)造方法如下:

  1. Timer(stmt='pass', setup='pass', timer=<timer function>) 

其中:

stmt: 要計(jì)時(shí)的語句或者函數(shù);

setup: 為stmt語句構(gòu)建環(huán)境的導(dǎo)入語句;

timer: 基于平臺(tái)的時(shí)間函數(shù)(timer function);

Timer()類有三個(gè)方法:

timeit(number=1000000): 返回stmt執(zhí)行number次的秒數(shù)(float);

repeat(repeat=3, number=1000000): repeat為重復(fù)整個(gè)測(cè)試的次數(shù),number為執(zhí)行stmt的次數(shù),返回以秒記錄的每個(gè)測(cè)試循環(huán)的耗時(shí)列表;

print_exc(file=None): 打印stmt的跟蹤信息。

此外,timeit 還提供了另外三個(gè)函數(shù)方便使用,參數(shù)和 Timer 差不多。

  1. timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000) 
  2.  
  3. timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=3, number=1000000) 
  4.  
  5. timeit.default_timer() 

profile

以上方法適用于比較簡單的場(chǎng)合,更復(fù)雜的情況下,可以用標(biāo)準(zhǔn)庫里面的profile或者cProfile,它可以統(tǒng)計(jì)程序里每一個(gè)函數(shù)的運(yùn)行時(shí)間,并且提供了可視化的報(bào)表。大多情況下,建議使用cProfile,它是profile的C實(shí)現(xiàn),適用于運(yùn)行時(shí)間長的程序。不過有的系統(tǒng)可能不支持cProfile,此時(shí)只好用profile。

可以用下面程序測(cè)試 timeit_profile() 函數(shù)運(yùn)行時(shí)間分配情況。

  1. import cProfile 
  2.  
  3. from time_profile import * 
  4.  
  5. cProfile.run("timeit_profile()"

這樣的輸出可能會(huì)很長,很多時(shí)候我們感興趣的可能只有耗時(shí)最多的幾個(gè)函數(shù),這個(gè)時(shí)候先將cProfile 的輸出保存到診斷文件中,然后用 pstats 定制更加有好的輸出(完整代碼在 gist 上)。

  1. cProfile.run("timeit_profile()""timeit"
  2.  
  3. p = pstats.Stats('timeit'
  4.  
  5. p.sort_stats('time'
  6.  
  7. p.print_stats(6) 

輸出結(jié)果如下:

 

 

如果覺得 pstas 使用不方便,還可以使用一些圖形化工具,比如 gprof2dot 來可視化分析 cProfile 的診斷結(jié)果。

vprof

vprof 也是一個(gè)不錯(cuò)的可視化工具,可以用來分析 Python 程序運(yùn)行時(shí)間情況。如下圖:

 

 

line_profiler

上面的測(cè)試最多統(tǒng)計(jì)到函數(shù)的執(zhí)行時(shí)間,很多時(shí)候我們想知道函數(shù)里面每一行代碼的執(zhí)行效率,這時(shí)候就可以用到 line_profiler 了。

line_profiler 的使用特別簡單,在需要監(jiān)控的函數(shù)前面加上 @profile 裝飾器。然后用它提供的 kernprof -l -v [source_code.py] 行進(jìn)行診斷。下面是一個(gè)簡單的測(cè)試程序 line_profile.py:

  1. from time_profile import slow_replace, slowest_replace 
  2.  
  3. for i in xrange(10000): 
  4.  
  5. slow_replace() 
  6.  
  7. slowest_replace() 

運(yùn)行后結(jié)果如下:

 

 

輸出每列的含義如下:

Line #: 行號(hào)

Hits: 當(dāng)前行執(zhí)行的次數(shù).

Time: 當(dāng)前行執(zhí)行耗費(fèi)的時(shí)間,單位為 “Timer unit:”

Per Hit: 平均執(zhí)行一次耗費(fèi)的時(shí)間.

% Time: 當(dāng)前行執(zhí)行時(shí)間占總時(shí)間的比例.

Line Contents: 當(dāng)前行的代碼

line_profiler 執(zhí)行時(shí)間的估計(jì)不是特別精確,不過可以用來分析當(dāng)前函數(shù)中哪些行是瓶頸。

責(zé)任編輯:趙立京 來源: Just For Fun
相關(guān)推薦

2014-07-28 09:52:14

PythonPython性能

2017-07-13 11:08:52

PythonC模塊性能分析

2010-05-20 09:20:06

MyEclipse8.

2015-09-14 10:41:51

PHP性能分析微觀分析

2015-08-18 11:44:02

PHP性能分析宏觀分析

2018-06-14 14:07:57

Pythonweb框架

2023-12-13 09:08:26

CPU性能分析Linux

2023-06-09 12:59:52

Python性能分析

2011-07-20 14:29:33

HBase

2011-03-22 13:00:47

Nagios

2022-04-12 12:35:02

Linux啟動(dòng)性能systemd

2020-10-27 11:35:31

PythonRedis數(shù)據(jù)庫

2019-10-31 11:50:19

MySQL數(shù)據(jù)庫Windows

2015-12-11 10:09:38

2023-09-18 16:14:35

性能測(cè)試開發(fā)

2022-09-28 14:13:03

Linux工具

2017-06-15 12:42:07

Linux常用性能分析命令

2022-01-26 15:07:04

bytrace工具OpenHarmon

2011-06-09 09:28:24

LevelDB

2019-02-21 15:01:45

PythonWeb部署
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)