Python: 使用 timeit.repeat() 來測試 function 的平均效能



前言

  • 先前開發服務需要隨機產生 hex 字串,原本是字串 slicing,後來發現可以用 f-string 的方式來處理
  • 改版前用 timeit 驗證了一下速度,發現相差不大,於是再用上了 repeat 來算平均與標準差,發現 f-string 的方式的確快了一咪咪,加上更好讀,所以就改了一版。
  • 這邊紀錄 timeit.repeat() 的使用方式。



說明

假設原本產 hex 隨機字串的方式是這樣:

  • hex() 以及字串 slicing
1
2
def generate_hex():
    return f"{hex(int(time.time()))[2:]}-{secrets.token_hex(3)}"

現在要評估新的方式:

  • f-string
1
2
def generate_hex():
    return f"{int(time.time()):x}-{secrets.token_hex(3)}"

為了方便測試,我把 function 改名字:

  • 原來的 function 叫 org_method()
  • 新的 function 叫 new_method()

然後用 timeit.repeat() 來測試。


Note

  • 原本是用 timeit 分別執行一次,但由於相差太小,所以才改用 repeat 來算多次執行的平均與標準差。



範例:timeit.repeat()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time
import secrets
import timeit
import statistics

def org_method():
    return f"{hex(int(time.time()))[2:]}-{secrets.token_hex(3)}"

def new_method():
    return f"{int(time.time()):x}-{secrets.token_hex(3)}"

# Number of times to run the function in each repeat
number = 20000
# Number of repeats
repeat = 200

for method in [org_method, new_method]:
    print(f"Testing {method.__name__}")

    # Measure the execution time
    times = timeit.repeat(f'{method.__name__}()', setup=f'from __main__ import {method.__name__}', repeat=repeat, number=number)

    # Calculate average and standard deviation
    average_time = statistics.mean(times)
    stdev_time = statistics.stdev(times)
    print(f"AVG: {average_time} seconds")
    print(f"SD:  {stdev_time} seconds")
    print()

執行結果:

Testing org_method
AVG: 0.011265084167243913 seconds
SD:  0.0018106434042022865 seconds

Testing new_method
AVG: 0.011380489610601216 seconds
SD:  0.00030189478410185694 seconds

發現用第一種方式 (hex() 以及 slicing):

  • 在執行 20000 次的情況下,平均快了 0.0001
  • 但是標準差多 0.0015

所以其實幾乎沒有差異。

考慮到 f-string 的可讀性,所以選擇了新的方式。


提高重用性

為了下次要用時方便,可以簡單把這段 code 包成一個 class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import secrets
import statistics
import time
import timeit


class TimeitRepeater:
    def __init__(self, number, repeat):
        self.number = number
        self.repeat = repeat
        self.result = {}

    def compare_func(self, *, org_func, new_func):
        self.result = {}
        for method in [org_func, new_func]:
            print(f"Testing {method.__name__} ...")
            # Measure the execution time
            times = timeit.repeat(
                f"{method.__name__}()",
                setup=f"from __main__ import {method.__name__}",
                repeat=self.repeat,
                number=self.number,
            )
            # Calculate average and standard deviation
            average_time = statistics.mean(times)
            stdev_time = statistics.stdev(times)
            self.result[method.__name__] = {
                "avg": average_time,
                "sd": stdev_time,
            }
        print()

    def print_result(self):
        print(
            f"Result: ({self.number} loops per repeat | repeats {self.repeat} times)\n"
        )
        for key in ["avg", "sd"]:
            for method, data in self.result.items():
                print(f"{method:>20} | {key.upper():>3}: {data[key]:.12f} seconds")
            print()

(也順便把 print() 的部分改成「平均與標準差分別比較」,比較清楚)

然後依據需求,可以自己填入要比較的 function:

像是:

1
2
3
4
5
6
def hex_slicing():
    return f"{hex(int(time.time()))[2:]}-{secrets.token_hex(3)}"


def by_f_string():
    return f"{int(time.time()):x}-{secrets.token_hex(3)}"

最後用這個 class 來比較:

1
2
3
tr = TimeitRepeater(10000, 100)
tr.compare_func(org_func=hex_slicing, new_func=by_f_string)
tr.print_result()

結果會像這樣

Testing hex_slicing ...
Testing by_f_string ...

Result: (10000 loops per repeat | repeats 100 times)

         hex_slicing | AVG: 0.008380977456 seconds
         by_f_string | AVG: 0.008256063755 seconds

         hex_slicing |  SD: 0.000167351771 seconds
         by_f_string |  SD: 0.000133428463 seconds



REF

  1. https://stackoverflow.com/questions/8220801/how-to-use-timeit-module

主題 StackJimmy 設計