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

像老大一樣優(yōu)化Python

開發(fā) 后端
我們應(yīng)該忘掉一些小的效率問題,在 97% 的情況下是這么說的:過早優(yōu)化是萬惡之源?!?Donald Knuth

我們應(yīng)該忘掉一些小的效率問題,在 97% 的情況下是這么說的:過早優(yōu)化是萬惡之源。—— Donald Knuth

如果不首先想想這句Knuth的名言,就開始進(jìn)行優(yōu)化工作是不明智的??墒牵愫芸鞂懗鰜砑尤胍恍┨匦缘拇a,可能會很丑陋,你需要注意了。這篇文章就是為這時候準(zhǔn)備的。

那么接下來就是一些很有用的工具和模式來快速優(yōu)化Python。它的主要目的很簡單:盡快發(fā)現(xiàn)瓶頸,修復(fù)它們并且確認(rèn)你修復(fù)了它們。

寫一個測試

在你開始優(yōu)化前,寫一個高級測試來證明原來代碼很慢。你可能需要采用一些最小值數(shù)據(jù)集來復(fù)現(xiàn)它足夠慢。通常一兩個顯示運(yùn)行時秒的程序就足夠處理一些改進(jìn)的地方了。

有一些基礎(chǔ)測試來保證你的優(yōu)化沒有改變原有代碼的行為也是很必要的。你也能夠在很多次運(yùn)行測試來優(yōu)化代碼的時候稍微修改這些測試的基準(zhǔn)。

那么現(xiàn)在,我們來來看看優(yōu)化工具把。

簡單的計時器

計時器很簡單,這是一個最靈活的記錄執(zhí)行時間的方法。你可以把它放到任何地方并且副作用很小。運(yùn)行你自己的計時器非常簡單,并且你可以將其定制,使它以你期望的方式工作。例如,你個簡單的計時器如下:

  1. import time 
  2.   
  3. def timefunc(f): 
  4.     def f_timer(*args, **kwargs): 
  5.         start = time.time() 
  6.         result = f(*args, **kwargs) 
  7.         end = time.time() 
  8.         print f.__name__, 'took', end - start, 'time' 
  9.         return result 
  10.     return f_timer 
  11.   
  12. def get_number(): 
  13.     for x in xrange(5000000): 
  14.         yield x 
  15.   
  16. @timefunc 
  17. def expensive_function(): 
  18.     for x in get_number(): 
  19.         i = x ^ x ^ x 
  20.     return 'some result!' 
  21.   
  22. # prints "expensive_function took 0.72583088875 seconds" 
  23. result = expensive_function() 

當(dāng)然,你可以用上下文管理來讓它功能更加強(qiáng)大,添加一些檢查點(diǎn)或者一些其他的功能:

  1. import time 
  2.   
  3. class timewith(): 
  4.     def __init__(self, name=''): 
  5.         self.name = name 
  6.         self.start = time.time() 
  7.   
  8.     @property 
  9.     def elapsed(self): 
  10.         return time.time() - self.start 
  11.   
  12.     def checkpoint(self, name=''): 
  13.         print '{timer} {checkpoint} took {elapsed} seconds'.format( 
  14.             timer=self.name, 
  15.             checkpoint=name, 
  16.             elapsed=self.elapsed, 
  17.         ).strip() 
  18.   
  19.     def __enter__(self): 
  20.         return self 
  21.   
  22.     def __exit__(self, type, value, traceback): 
  23.         self.checkpoint('finished'
  24.         pass 
  25.   
  26. def get_number(): 
  27.     for x in xrange(5000000): 
  28.         yield x 
  29.   
  30. def expensive_function(): 
  31.     for x in get_number(): 
  32.         i = x ^ x ^ x 
  33.     return 'some result!' 
  34.   
  35. # prints something like: 
  36. # fancy thing done with something took 0.582462072372 seconds 
  37. # fancy thing done with something else took 1.75355315208 seconds 
  38. # fancy thing finished took 1.7535982132 seconds 
  39. with timewith('fancy thing') as timer: 
  40.     expensive_function() 
  41.     timer.checkpoint('done with something'
  42.     expensive_function() 
  43.     expensive_function() 
  44.     timer.checkpoint('done with something else'
  45.   
  46. # or directly 
  47. timer = timewith('fancy thing'
  48. expensive_function() 
  49. timer.checkpoint('done with something'

計時器還需要你做一些挖掘。包裝一些更高級的函數(shù),并且確定瓶頸在哪,然后深入的函數(shù)里,能夠不停的重現(xiàn)。當(dāng)你發(fā)現(xiàn)一些不合適的代碼,修復(fù)它,然后測試一遍以確認(rèn)它被修復(fù)了。

一些小技巧:不要忘了好用的timeit模塊!它對小塊代碼做基準(zhǔn)測試而不是實(shí)際調(diào)查更加有用。

  • Timer 優(yōu)點(diǎn):很容易理解和實(shí)現(xiàn)。也非常容易在修改后進(jìn)行比較。對于很多語言都適用。
  • Timer 缺點(diǎn):有時候?qū)τ诜浅?fù)雜的代碼有點(diǎn)過于簡單,你可能會花更多時間放置或移動引用代碼而不是修復(fù)問題!

#p#

內(nèi)建優(yōu)化器

啟用內(nèi)建的優(yōu)化器就像是用一門大炮。它非常強(qiáng)大,但是有點(diǎn)不太好用,使用和解釋起來比較復(fù)雜。

你可以了解更多關(guān)于profile模塊的東西,但是它的基礎(chǔ)是非常簡單的:你能夠啟用和禁用優(yōu)化器,而且它能打印所有的函數(shù)調(diào)用和執(zhí)行時間。它能給你編譯和打印出輸出。一個簡單的裝飾器如下:

  1. import cProfile 
  2.   
  3. def do_cprofile(func): 
  4.     def profiled_func(*args, **kwargs): 
  5.         profile = cProfile.Profile() 
  6.         try
  7.             profile.enable() 
  8.             result = func(*args, **kwargs) 
  9.             profile.disable() 
  10.             return result 
  11.         finally
  12.             profile.print_stats() 
  13.     return profiled_func 
  14.   
  15. def get_number(): 
  16.     for x in xrange(5000000): 
  17.         yield x 
  18.   
  19. @do_cprofile 
  20. def expensive_function(): 
  21.     for x in get_number(): 
  22.         i = x ^ x ^ x 
  23.     return 'some result!' 
  24.   
  25. # perform profiling 
  26. result = expensive_function() 

在上面代碼的情況下,你應(yīng)該看到有些東西在終端打印出來,打印的內(nèi)容如下:

  1. 5000003 function calls in 1.626 seconds 
  2.   
  3.    Ordered by: standard name 
  4.   
  5.    ncalls  tottime  percall  cumtime  percall filename:lineno(function) 
  6.   5000001    0.571    0.000    0.571    0.000 timers.py:92(get_number) 
  7.         1    1.055    1.055    1.626    1.626 timers.py:96(expensive_function) 
  8.         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects} 

你可以看到,它給出了不同函數(shù)的調(diào)用次數(shù),但它遺漏了一些關(guān)鍵的信息:是哪個函數(shù)讓運(yùn)行這么慢?

可是,這對于基礎(chǔ)優(yōu)化來說是個好的開始。有時候甚至能用更少的精力找到解決方案。我經(jīng)常用它來在深入挖掘究竟是哪個函數(shù)慢或者調(diào)用次數(shù)過多之前來調(diào)試程序。

  • 內(nèi)建優(yōu)點(diǎn):沒有額外的依賴并且非常快。對于快速的高等級檢查非常有用。
  • 內(nèi)建缺點(diǎn):信息相對有限,需要進(jìn)一步的調(diào)試;報告有點(diǎn)不太直接,尤其是對于復(fù)雜的代碼。

Line Profiler

如果內(nèi)建的優(yōu)化器是一門大炮,那么line profiler可以看作是一門離子加農(nóng)炮。它非常的重量級和強(qiáng)大。

在這個例子里,我們會用非常棒的line_profiler庫。為了容易使用,我們會再次用裝飾器包裝一下,這種簡單的方法也可以防止把它放在生產(chǎn)代碼里。

  1. try
  2.     from line_profiler import LineProfiler 
  3.   
  4.     def do_profile(follow=[]): 
  5.         def inner(func): 
  6.             def profiled_func(*args, **kwargs): 
  7.                 try
  8.                     profiler = LineProfiler() 
  9.                     profiler.add_function(func) 
  10.                     for f in follow: 
  11.                         profiler.add_function(f) 
  12.                     profiler.enable_by_count() 
  13.                     return func(*args, **kwargs) 
  14.                 finally
  15.                     profiler.print_stats() 
  16.             return profiled_func 
  17.         return inner 
  18.   
  19. except ImportError: 
  20.     def do_profile(follow=[]): 
  21.         "Helpful if you accidentally leave in production!" 
  22.         def inner(func): 
  23.             def nothing(*args, **kwargs): 
  24.                 return func(*args, **kwargs) 
  25.             return nothing 
  26.         return inner 
  27.   
  28. def get_number(): 
  29.     for x in xrange(5000000): 
  30.         yield x 
  31.   
  32. @do_profile(follow=[get_number]) 
  33. def expensive_function(): 
  34.     for x in get_number(): 
  35.         i = x ^ x ^ x 
  36.     return 'some result!' 
  37.   
  38. result = expensive_function() 
  39.  
  40. 如果你運(yùn)行上面的代碼,你就可以看到一下的報告: 
  41. 1 
  42. 2 
  43. 3 
  44. 4 
  45. 5 
  46. 6 
  47. 7 
  48. 8 
  49. 9 
  50. 10 
  51. 11 
  52. 12 
  53. 13 
  54. 14 
  55. 15 
  56. 16 
  57. 17 
  58. 18 
  59. 19 
  60. 20 
  61. 21 
  62. 22 
  63.      
  64. Timer unit: 1e-06 s 
  65.   
  66. File: test.py 
  67. Function: get_number at line 43 
  68. Total time: 4.44195 s 
  69.   
  70. Line #      Hits         Time  Per Hit   % Time  Line Contents 
  71. ============================================================== 
  72.     43                                           def get_number(): 
  73.     44   5000001      2223313      0.4     50.1      for x in xrange(5000000): 
  74.     45   5000000      2218638      0.4     49.9          yield x 
  75.   
  76. File: test.py 
  77. Function: expensive_function at line 47 
  78. Total time: 16.828 s 
  79.   
  80. Line #      Hits         Time  Per Hit   % Time  Line Contents 
  81. ============================================================== 
  82.     47                                           def expensive_function(): 
  83.     48   5000001     14090530      2.8     83.7      for x in get_number(): 
  84.     49   5000000      2737480      0.5     16.3          i = x ^ x ^ x 
  85.     50         1            0      0.0      0.0      return 'some result!' 

你可以看到,有一個非常詳細(xì)的報告,能讓你完全洞悉代碼運(yùn)行的情況。不想內(nèi)建的cProfiler,它能計算話在語言核心特性的時間,比如循環(huán)和導(dǎo)入并且給出在不同的行花費(fèi)的時間。

這些細(xì)節(jié)能讓我們更容易理解函數(shù)內(nèi)部。如果你在研究某個第三方庫,你可以直接將其導(dǎo)入并加上裝飾器來分析它。

一些小技巧:只裝飾你的測試函數(shù)并將問題函數(shù)作為接下來的參數(shù)。

  •  Line Profiler 優(yōu)點(diǎn):有非常直接和詳細(xì)的報告。能夠追蹤第三方庫里的函數(shù)。
  •  Line Profiler 缺點(diǎn):因?yàn)樗鼤尨a比真正運(yùn)行時慢很多,所以不要用它來做基準(zhǔn)測試。這是額外的需求。

總結(jié)和最佳實(shí)踐

你應(yīng)該用更簡單的工具來對測試用例進(jìn)行根本的檢查,并且用更慢但能顯示更多細(xì)節(jié)的line_profiler來深入到函數(shù)內(nèi)部。

九成情況下,你可能會發(fā)現(xiàn)在一個函數(shù)里循環(huán)調(diào)用或一個錯誤的數(shù)據(jù)結(jié)構(gòu)消耗了90%的時間。一些調(diào)整工具是非常適合你的。

如果你仍然覺得這太慢,而是用一些你自己的秘密武器,如比較屬性訪問技術(shù)或調(diào)整平衡檢查技術(shù)。你也可以用如下的方法:

1.忍受緩慢或者緩存它們

2.重新思考整個實(shí)現(xiàn)

3.更多使用優(yōu)化的數(shù)據(jù)結(jié)構(gòu)

4.寫一個C擴(kuò)展

注意了,優(yōu)化代碼是種罪惡的快感!用合適的方法來為你的Python代碼加速很有意思,但是注意不要破壞了本身的邏輯??勺x的代碼比運(yùn)行速度更重要。先把它緩存起來再進(jìn)行優(yōu)化其實(shí)更好。

原文鏈接:https://zapier.com/engineering/profiling-python-boss/

譯文鏈接:http://blog.jobbole.com/54057/

責(zé)任編輯:陳四芳 來源: 伯樂在線
相關(guān)推薦

2013-12-17 09:02:03

Python調(diào)試

2023-05-23 13:59:41

RustPython程序

2021-05-20 08:37:32

multiprocesPython線程

2023-04-05 14:19:07

FlinkRedisNoSQL

2014-02-27 13:10:57

Visual Stud調(diào)試

2017-05-22 10:33:14

PythonJuliaCython

2022-12-21 15:56:23

代碼文檔工具

2020-08-25 08:56:55

Pythonawk字符串

2014-09-22 09:27:57

Python

2015-03-16 12:50:44

2013-08-22 10:17:51

Google大數(shù)據(jù)業(yè)務(wù)價值

2011-01-18 10:45:16

喬布斯

2012-06-08 13:47:32

Wndows 8Vista

2015-02-05 13:27:02

移動開發(fā)模塊SDK

2012-03-21 10:15:48

RIM越獄

2021-09-07 10:29:11

JavaScript模塊CSS

2021-12-14 19:40:07

Node路由Vue

2011-10-24 13:07:00

2012-06-14 09:48:11

OpenStackLinux

2015-04-09 11:27:34

點(diǎn)贊
收藏

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