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

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

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

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


Basic examples
==============

以下範例展示了如何使用Command-line interface來比較三個不同的運算式：

   $ 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 interface來實現：

   >>> 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 interface傳遞可呼叫物件：

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

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


Python interface
================

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

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, target_time=None)

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

      This is a convenience function that calls "timeit()" repeatedly
      so that the total time >= *Timer.target_time* seconds, returning
      the eventual (number of loops, time taken for that number of
      loops). It calls "timeit()" with increasing numbers from the
      sequence 1, 2, 5, 10, 20, 50, ... until the time taken is at
      least *target_time* seconds.

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

      在 3.6 版被加入.

      在 3.15 版的變更: The optional *target_time* parameter was
      added.

   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"。


Command-line interface
======================

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

   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 版被加入.

-t, --target-time=T

   if "--number" is 0, the code will run until it takes at least this
   many seconds (default: 0.2)

   在 3.15 版被加入.

-v, --verbose

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

-h, --help

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

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

If "-n" is not given, a suitable number of loops is calculated by
trying increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ...
until the total time is at least "--target-time" seconds (default:
0.2).

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

備註:

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

在 3.15 版被加入: Output is in color by default and can be controlled
using environment variables.


範例
====

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

   $ 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()))
