亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

使用優(yōu)化器來提升Python程序的執(zhí)行效率的教程

 更新時間:2015年04月02日 16:27:48   作者:Bryan Helmig  
這篇文章主要介紹了使用優(yōu)化器來提升Python程序的執(zhí)行效率的教程,包括編寫計時器和使用內(nèi)建的優(yōu)化器等,需要的朋友可以參考下

如果不首先想想這句Knuth的名言,就開始進(jìn)行優(yōu)化工作是不明智的??墒?,你很快寫出來加入一些特性的代碼,可能會很丑陋,你需要注意了。這篇文章就是為這時候準(zhǔn)備的。

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

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

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

那么現(xiàn)在,我們來來看看優(yōu)化工具把。
簡單的計時器

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

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
 
@timefunc
def expensive_function():
 for x in get_number():
  i = x ^ x ^ x
 return 'some result!'
 
# prints "expensive_function took 0.72583088875 seconds"
result = expensive_function()

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

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 seconds
with timewith('fancy thing') as timer:
 expensive_function()
 timer.checkpoint('done with something')
 expensive_function()
 expensive_function()
 timer.checkpoint('done with something else')
 
# or directly
timer = timewith('fancy thing')
expensive_function()
timer.checkpoint('done with something')

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

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

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

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

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

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

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_cprofile
def expensive_function():
 for x in get_number():
  i = x ^ x ^ x
 return 'some result!'
 
# perform profiling
result = expensive_function()

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

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}

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

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

  •     內(nèi)建優(yōu)點:沒有額外的依賴并且非??臁τ诳焖俚母叩燃墮z查非常有用。
  •     內(nèi)建缺點:信息相對有限,需要進(jìn)一步的調(diào)試;報告有點不太直接,尤其是對于復(fù)雜的代碼。

Line Profiler

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

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

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.py
Function: get_number at line 43
Total 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.py
Function: expensive_function at line 47
Total 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!'

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

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

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

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

總結(jié)和最佳實踐

你應(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.重新思考整個實現(xiàn)

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

4.寫一個C擴展

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

相關(guān)文章

最新評論