使用最佳化器來提升Python程式的執行效率的教程

來源:互聯網
上載者:User
如果不首先想想這句Knuth的名言,就開始進行最佳化工作是不明智的。可是,你很快寫出來加入一些特性的代碼,可能會很醜陋,你需要注意了。這篇文章就是為這時候準備的。

那麼接下來就是一些很有用的工具和模式來快速最佳化Python。它的主要目的很簡單:儘快發現瓶頸,修複它們並且確認你修複了它們。
寫一個測試

在你開始最佳化前,寫一個進階測試來證明原來代碼很慢。你可能需要採用一些最小值資料集來複現它足夠慢。通常一兩個顯示運行時秒的程式就足夠處理一些改進的地方了。

有一些基礎測試來保證你的最佳化沒有改變原有代碼的行為也是很必要的。你也能夠在很多次運行測試來最佳化代碼的時候稍微修改這些測試的基準。

那麼現在,我們來來看看最佳化工具把。
簡單的計時器

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

import time def timefunc(f): def f_timer(*args, **kwargs):  start = time.time()  result = f(*args, **kwargs)  end = time.time()  print f.__name__, 'took', end - start, 'time'  return result return f_timer def get_number(): for x in xrange(5000000):  yield x @timefuncdef expensive_function(): for x in get_number():  i = x ^ x ^ x return 'some result!' # prints "expensive_function took 0.72583088875 seconds"result = expensive_function()

當然,你可以用上下文管理來讓它功能更加強大,添加一些檢查點或者一些其他的功能:

import time class timewith(): def __init__(self, name=''):  self.name = name  self.start = time.time()  @property def elapsed(self):  return time.time() - self.start  def checkpoint(self, name=''):  print '{timer} {checkpoint} took {elapsed} seconds'.format(   timer=self.name,   checkpoint=name,   elapsed=self.elapsed,  ).strip()  def __enter__(self):  return self  def __exit__(self, type, value, traceback):  self.checkpoint('finished')  pass def get_number(): for x in xrange(5000000):  yield x def expensive_function(): for x in get_number():  i = x ^ x ^ x return 'some result!' # prints something like:# fancy thing done with something took 0.582462072372 seconds# fancy thing done with something else took 1.75355315208 seconds# fancy thing finished took 1.7535982132 secondswith timewith('fancy thing') as timer: expensive_function() timer.checkpoint('done with something') expensive_function() expensive_function() timer.checkpoint('done with something else') # or directlytimer = timewith('fancy thing')expensive_function()timer.checkpoint('done with something')

計時器還需要你做一些挖掘。封裝一些更進階的函數,並且確定瓶頸在哪,然後深入的函數裡,能夠不停的重現。當你發現一些不合適的代碼,修複它,然後測試一遍以確認它被修複了。

一些小技巧:不要忘了好用的timeit模組!它對小塊代碼做基準測試而不是實際調查更加有用。

  • Timer 優點:很容易理解和實現。也非常容易在修改後進行比較。對於很多語言都適用。
  • Timer 缺點:有時候對於非常複雜的代碼有點過於簡單,你可能會花更多時間放置或移動引用代碼而不是修複問題!

內建最佳化器

啟用內建的最佳化器就像是用一門大炮。它非常強大,但是有點不太好用,使用和解釋起來比較複雜。

你可以瞭解更多關於profile模組的東西,但是它的基礎是非常簡單的:你能夠啟用和禁用最佳化器,而且它能列印所有的函數調用和執行時間。它能給你編譯和列印出輸出。一個簡單的裝飾器如下:

import cProfile def do_cprofile(func): def profiled_func(*args, **kwargs):  profile = cProfile.Profile()  try:   profile.enable()   result = func(*args, **kwargs)   profile.disable()   return result  finally:   profile.print_stats() return profiled_func def get_number(): for x in xrange(5000000):  yield x @do_cprofiledef expensive_function(): for x in get_number():  i = x ^ x ^ x return 'some result!' # perform profilingresult = expensive_function()

在上面代碼的情況下,你應該看到有些東西在終端列印出來,列印的內容如下:

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

你可以看到,它給出了不同函數的調用次數,但它遺漏了一些關鍵的資訊:是哪個函數讓運行這麼慢?

可是,這對於基礎最佳化來說是個好的開始。有時候甚至能用更少的精力找到解決方案。我經常用它來在深入挖掘究竟是哪個函數慢或者調用次數過多之前來偵錯工具。

  • 內建優點:沒有額外的依賴並且非常快。對於快速的高等級檢查非常有用。
  • 內建缺點:資訊相對有限,需要進一步的調試;報告有點不太直接,尤其是對於複雜的代碼。

Line Profiler

如果內建的最佳化器是一門大炮,那麼line profiler可以看作是一門離子加農炮。它非常的重量級和強大。

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

try: from line_profiler import LineProfiler  def do_profile(follow=[]):  def inner(func):   def profiled_func(*args, **kwargs):    try:     profiler = LineProfiler()     profiler.add_function(func)     for f in follow:      profiler.add_function(f)     profiler.enable_by_count()     return func(*args, **kwargs)    finally:     profiler.print_stats()   return profiled_func  return inner except ImportError: def do_profile(follow=[]):  "Helpful if you accidentally leave in production!"  def inner(func):   def nothing(*args, **kwargs):    return func(*args, **kwargs)   return nothing  return inner def get_number(): for x in xrange(5000000):  yield x @do_profile(follow=[get_number])def expensive_function(): for x in get_number():  i = x ^ x ^ x return 'some result!' result = expensive_function()

如果你運行上面的代碼,你就可以看到一下的報告:

Timer unit: 1e-06 s File: test.pyFunction: get_number at line 43Total time: 4.44195 s Line #  Hits   Time Per Hit % Time Line Contents============================================================== 43           def get_number(): 44 5000001  2223313  0.4  50.1  for x in xrange(5000000): 45 5000000  2218638  0.4  49.9   yield x File: test.pyFunction: expensive_function at line 47Total time: 16.828 s Line #  Hits   Time Per Hit % Time Line Contents============================================================== 47           def expensive_function(): 48 5000001  14090530  2.8  83.7  for x in get_number(): 49 5000000  2737480  0.5  16.3   i = x ^ x ^ x 50   1   0  0.0  0.0  return 'some result!'

你可以看到,有一個非常詳細的報告,能讓你完全洞悉代碼啟動並執行情況。不想內建的cProfiler,它能計算話在語言核心特性的時間,比如迴圈和匯入並且給出在不同的行花費的時間。

這些細節能讓我們更容易理解函數內部。如果你在研究某個第三方庫,你可以直接將其匯入並加上裝飾器來分析它。

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

  • Line Profiler 優點:有非常直接和詳細的報告。能夠追蹤第三方庫裡的函數。
  • Line Profiler 缺點:因為它會讓代碼比真正運行時慢很多,所以不要用它來做基準測試。這是額外的需求。

總結和最佳實務

你應該用更簡單的工具來對測試案例進行根本的檢查,並且用更慢但能顯示更多細節的line_profiler來深入到函數內部。

九成情況下,你可能會發現在一個函數裡迴圈調用或一個錯誤的資料結構消耗了90%的時間。一些調整工具是非常適合你的。

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

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

2.重新思考整個實現

3.更多使用最佳化的資料結構

4.寫一個C擴充

注意了,最佳化代碼是種罪惡的快感!用合適的方法來為你的Python代碼加速很有意思,但是注意不要破壞了本身的邏輯。可讀的代碼比運行速度更重要。先把它緩衝起來再進行最佳化其實更好。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.