前言
先前開發服務需要隨機產生 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 ) } "
Copy
現在要評估新的方式:
1
2
def generate_hex ():
return f " { int ( time. time()) : x } - { secrets. token_hex( 3 ) } "
Copy 為了方便測試,我把 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 ()
Copy 執行結果:
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 ()
Copy (也順便把 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 ) } "
Copy 最後用這個 class 來比較:
1
2
3
tr = TimeitRepeater( 10000 , 100 )
tr. compare_func( org_func= hex_slicing, new_func= by_f_string)
tr. print_result()
Copy 結果會像這樣
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