"timeit" --- 測量小量程式片段的執行時間
***************************************

**原始碼：**Lib/timeit.py

======================================================================

該模組提供了一種對少量 Python 程式碼進行計時的簡單方法。它有一個命令列
介面和一個可呼叫介面，它避免了許多測量執行時間的常見陷阱。另請參閱由
O'Reilly 出版的 *Python 錦囊妙計 (Python Cookbook)* 第二版中 Tim
Peters 所寫的「演算法」章節的介紹。


基礎範例
========

以下範例展示了如何使用命令列介面來比較三個不同的運算式：

   $ python -m timeit "'-'.join(str(n) for n in range(100))"
   10000 loops, best of 5: 30.2 usec per loop
   $ python -m timeit "'-'.join([str(n) for n in range(100)])"
   10000 loops, best of 5: 27.5 usec per loop
   $ python -m timeit "'-'.join(map(str, range(100)))"
   10000 loops, best of 5: 23.2 usec per loop

這可以透過 Python 介面來實現：

   >>> import timeit
   >>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
   0.3018611848820001
   >>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
   0.2727368790656328
   >>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
   0.23702679807320237

也可以在 Python 介面傳遞可呼叫物件：

   >>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
   0.19665591977536678

但請注意，僅當使用命令列介面時 "timeit()" 才會自動確定重複次數。在範例
章節中有更進階的範例。


Python 介面
===========

該模組定義了三個便利函式和一個公開類別：

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

   使用給定的陳述式、*setup* 程式碼和 *timer* 函式建立一個 "Timer" 實
   例，並執行其 "timeit()" 方法 *number* 次。可選的 *globals* 引數指定
   會在其中執行程式碼的命名空間。

   在 3.5 版的變更: 新增 *globals* 選用參數。

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)

   使用給定的陳述式、*setup* 程式碼和 *timer* 函式建立一個 "Timer" 實
   例，並使用給定的 *repeat* 計數和 *number* 來運行其 "repeat()" 方法
   。可選的 *globals* 引數指定會在其中執行程式碼的命名空間。

   在 3.5 版的變更: 新增 *globals* 選用參數。

   在 3.7 版的變更: *repeat* 的預設值從 3 更改為 5。

timeit.default_timer()

   預設計時器始終為 time.perf_counter()，會回傳浮點秒數。另一種方法是
   time.perf_counter_ns，會回傳整數奈秒。

   在 3.3 版的變更: "time.perf_counter()" 現在是預設計時器。

class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)

   用於計時小程式碼片段執行速度的類別。

   建構函式接受一個要計時的陳述式、一個用於設定的附加陳述式和一個計時
   器函式。兩個陳述式都預設為 "'pass'"；計時器函式會與平台相依（請參閱
   模組文件字串 (doc string)）。*stmt* 和 *setup* 還可以包含由 ";" 或
   換行符號分隔的多個陳述式，只要它們不包含多行字串文字即可。預設情況
   下，該陳述式將在 timeit 的命名空間內執行；可以透過將命名空間傳遞給
   *globals* 來控制此行為。

   要測量第一個陳述式的執行時間，請使用 "timeit()" 方法。"repeat()" 和
   "autorange()" 方法是多次呼叫 "timeit()" 的便捷方法。

   *setup* 的執行時間不包含在總體運行計時中。

   *stmt* 和 *setup* 參數還可以接受無需引數即可呼叫的物件。這會把對它
   們的呼叫嵌入到計時器函式中，然後由 "timeit()" 去執行。請注意，在這
   種情況下，因有額外的函式呼叫，時間開銷 (timing overhead) 會稍大一些
   。

   在 3.5 版的變更: 新增 *globals* 選用參數。

   timeit(number=1000000)

      主陳述式執行 *number* 次的時間。這將執行一次設定陳述式，然後回傳
      多次執行主陳述式所需的時間。預設計時器以浮點形式回傳秒數，引數是
      迴圈的次數，預設為一百萬次。要使用的主陳述式、設定陳述式和計時器
      函式會被傳遞給建構函式。

      備註:

        預設情況下 "timeit()" 在計時期間會暫時關閉*垃圾回收*。這種方法
        的優點是它使獨立時序更具可比較性，缺點是 GC 可能是被測函式性能
        的重要組成部分。如果是這樣，可以將 GC 作為 *setup* 字串中的第
        一個陳述式以重新啟用。例如：

           timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()

   autorange(callback=None)

      自動決定呼叫 "timeit()" 次數。

      這是一個便捷函式，它重複呼叫 "timeit()" 以使得總時間 >= 0.2 秒，
      再回傳最終結果（迴圈數、該迴圈數所花費的時間）。它以 1、2、5、10
      、20、50... 的順序遞增數字來呼叫 "timeit()" 直到所用時間至少為
      0.2 秒。

      如果有給定 *callback* 且不是 "None"，則每次試驗後都會使用兩個引
      數來呼叫它："callback(number, time_taken)"。

      在 3.6 版被加入.

   repeat(repeat=5, number=1000000)

      呼叫 "timeit()" 數次。

      這是一個方便的函式，它會重複呼叫 "timeit()" 並回傳結果列表。第一
      個引數指定呼叫 "timeit()" 的次數，第二個引數指定 "timeit()" 的
      *number* 引數。

      備註:

        人們很容易根據結果向量來計算出平均值和標準差並將其作為依歸，然
        而這並不是很有用。在典型情況下，最低值給出了機器運行給定程式碼
        片段的速度的下限；結果向量中較高的值通常不是由 Python 速度的變
        化所引起，而是由干擾計時精度的其他行程造成的。因此，結果中的
        "min()" 可能是你應該感興趣的唯一數字。在解讀該數據後，你應該查
        看整個向量並以常識判讀而非單純仰賴統計資訊。

      在 3.7 版的變更: *repeat* 的預設值從 3 更改為 5。

   print_exc(file=None)

      從計時程式碼印出回溯 (traceback) 的輔助函式。

      典型用法：

         t = Timer(...)       # 在 try/except 之外
         try:
             t.timeit(...)    # 或 t.repeat(...)
         except Exception:
             t.print_exc()

      相對於標準回溯的優點是，已編譯模板中的原始程式碼會被顯示出來。可
      選的 *file* 引數指定回溯的發送位置；它預設為 "sys.stderr"。


命令列介面
==========

當從命令列作為程式呼叫時，請使用以下形式：

   python -m timeit [-n N] [-r N] [-u U] [-s S] [-p] [-v] [-h] [statement ...]

其中之以下選項：

-n N, --number=N

   執行 'statement' 多少次

-r N, --repeat=N

   計時器重複多少次（預設 5）

-s S, --setup=S

   會在一開始執行一次的陳述式（預設為 "pass"）

-p, --process

   若要測量行程時間 (process time) 而非掛鐘時間 (wallclock time)，請使
   用 "time.process_time()" 而不是預設的 "time.perf_counter()"

   在 3.3 版被加入.

-u, --unit=U

   指定定時器輸出的時間單位；可以選擇 "nsec"、"usec"、"msec" 或 "sec"

   在 3.5 版被加入.

-v, --verbose

   印出原始計時結果；重複執行以獲得更高的數字精度

-h, --help

   印出一條簡短的使用訊息並退出

可以透過將每一列陳述式指定為單獨引數來給定多列陳述式；可透過將引數括在
引號中並使用前導空格以實現縮進列 (indented lines)。多個 "-s" 選項的作
用類似。

如果沒有給定 "-n"，則透過嘗試從序列 1, 2, 5, 10, 20, 50, ... 中增加數
字來計算合適的迴圈次數，直到總時間至少為 0.2 秒。

"default_timer()" 測量可能會受到同一台機器上運行的其他程式的影響，因此
，當需要精確計時時，最好的做法是重複計時幾次並使用最佳時間。"-r" 選項
對此很有用；在大多數情況下，預設的重複 5 次可能就足夠了。你可以使用
"time.process_time()" 來測量 CPU 時間。

備註:

  執行 pass 陳述式會產生一定的基本開銷。這裡的程式碼並不試圖隱藏它，但
  你應該意識到它的存在。可以透過不帶引數呼叫程式來測量這個基本開銷，且
  不同 Python 版本之間的基本開銷可能有所不同。


範例
====

可以提供一個僅會在開始時執行一次的設定陳述式：

   $ python -m timeit -s "text = 'sample string'; char = 'g'" "char in text"
   5000000 loops, best of 5: 0.0877 usec per loop
   $ python -m timeit -s "text = 'sample string'; char = 'g'" "text.find(char)"
   1000000 loops, best of 5: 0.342 usec per loop

輸出中包含三個欄位。迴圈計數，它告訴你每次計時迴圈內重複運行陳述式主體
的次數。重複計數（「最好的 5 次」）告訴你計時迴圈重複了多少次。以及最
後陳述式主體在計時迴圈的最佳的幾次重複執行內平均花費的時間。也就是說，
最快的幾次重複執行所花費的總時間除以迴圈計數。

   >>> import timeit
   >>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"')
   0.41440500499993504
   >>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"')
   1.7246671520006203

同樣可以使用 "Timer" 類別及其方法來完成：

   >>> import timeit
   >>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
   >>> t.timeit()
   0.3955516149999312
   >>> t.repeat()
   [0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

以下範例展示如何對包含多行的運算式進行計時。這裡我們使用 "hasattr()"
與 "try"/"except" 來測試缺失和存在之物件屬性並比較其花費 (cost)：

   $ python -m timeit "try:" "  str.__bool__" "except AttributeError:" "  pass"
   20000 loops, best of 5: 15.7 usec per loop
   $ python -m timeit "if hasattr(str, '__bool__'): pass"
   50000 loops, best of 5: 4.26 usec per loop

   $ python -m timeit "try:" "  int.__bool__" "except AttributeError:" "  pass"
   200000 loops, best of 5: 1.43 usec per loop
   $ python -m timeit "if hasattr(int, '__bool__'): pass"
   100000 loops, best of 5: 2.23 usec per loop

   >>> import timeit
   >>> # attribute is missing
   >>> s = """\
   ... try:
   ...     str.__bool__
   ... except AttributeError:
   ...     pass
   ... """
   >>> timeit.timeit(stmt=s, number=100000)
   0.9138244460009446
   >>> s = "if hasattr(str, '__bool__'): pass"
   >>> timeit.timeit(stmt=s, number=100000)
   0.5829014980008651
   >>>
   >>> # attribute is present
   >>> s = """\
   ... try:
   ...     int.__bool__
   ... except AttributeError:
   ...     pass
   ... """
   >>> timeit.timeit(stmt=s, number=100000)
   0.04215312199994514
   >>> s = "if hasattr(int, '__bool__'): pass"
   >>> timeit.timeit(stmt=s, number=100000)
   0.08588060699912603

要讓 "timeit" 模組存取你定義的函式，你可以傳遞一個包含 import 陳述式的
*setup* 參數：

   def test():
       """一個笨笨的測試函式"""
       L = [i for i in range(100)]

   if __name__ == '__main__':
       import timeit
       print(timeit.timeit("test()", setup="from __main__ import test"))

另一種選擇是將 "globals()" 傳遞給 *globals* 參數，這將導致程式碼在目前
的全域命名空間中執行，這比單獨指定 import 更方便：

   def f(x):
       return x**2
   def g(x):
       return x**4
   def h(x):
       return x**8

   import timeit
   print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))
