前言
- 先前開發服務需要隨機產生 hex 字串,原本是字串 slicing,後來發現可以用 f-string 的方式來處理
- 改版前用
timeit
驗證了一下速度,發現相差不大,於是再用上了 repeat
來算平均與標準差,發現 f-string 的方式的確快了一咪咪,加上更好讀,所以就改了一版。
- 這邊紀錄
timeit.repeat()
的使用方式。
說明
假設原本產 hex 隨機字串的方式是這樣:
1
2
|
def generate_hex():
return f"{hex(int(time.time()))[2:]}-{secrets.token_hex(3)}"
|
現在要評估新的方式:
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
- https://stackoverflow.com/questions/8220801/how-to-use-timeit-module