ฉันเข้าใจแนวคิดของสิ่งที่timeit
ทำ แต่ฉันไม่แน่ใจว่าจะนำไปใช้ในโค้ดของฉันได้อย่างไร
ฉันจะเปรียบเทียบทั้งสองฟังก์ชันพูดinsertion_sort
และtim_sort
กับได้timeit
อย่างไร
ฉันเข้าใจแนวคิดของสิ่งที่timeit
ทำ แต่ฉันไม่แน่ใจว่าจะนำไปใช้ในโค้ดของฉันได้อย่างไร
ฉันจะเปรียบเทียบทั้งสองฟังก์ชันพูดinsertion_sort
และtim_sort
กับได้timeit
อย่างไร
คำตอบ:
วิธีtimeitทำงานคือการเรียกใช้รหัสการติดตั้งครั้งเดียวแล้วโทรซ้ำไปยังชุดของคำสั่ง ดังนั้นหากคุณต้องการทดสอบการเรียงลำดับจำเป็นต้องมีการดูแลบางอย่างเพื่อให้การผ่านหนึ่งครั้งในการเรียงลำดับในสถานที่ไม่ส่งผลต่อการผ่านครั้งต่อไปที่มีการเรียงลำดับข้อมูลแล้ว (แน่นอนว่าจะทำให้Timsortส่องแสงจริงๆ เมื่อข้อมูลได้รับคำสั่งบางส่วนแล้ว)
นี่คือตัวอย่างของวิธีการตั้งค่าการทดสอบสำหรับการเรียงลำดับ:
>>> import timeit
>>> setup = '''
import random
random.seed('slartibartfast')
s = [random.random() for i in range(1000)]
timsort = list.sort
'''
>>> print min(timeit.Timer('a=s[:]; timsort(a)', setup=setup).repeat(7, 1000))
0.334147930145
โปรดทราบว่าชุดข้อความสั่งทำสำเนาใหม่ของข้อมูลที่ไม่เรียงลำดับในทุกรอบ
นอกจากนี้ให้สังเกตเทคนิคการจับเวลาในการเรียกใช้ชุดการวัดเจ็ดครั้งและเก็บเฉพาะเวลาที่ดีที่สุดซึ่งจะช่วยลดความผิดเพี้ยนจากการวัดได้เนื่องจากกระบวนการอื่นที่ทำงานอยู่ในระบบของคุณ
นี่คือเคล็ดลับสำหรับการใช้ timeit อย่างถูกต้อง หวังว่านี่จะช่วย :-)
.repeat(7,1000)
ทำสิ่งนี้อยู่แล้ว (โดยใช้เมล็ดพันธุ์เดียวกัน)! ดังนั้นทางออกของคุณคือ IMO ที่สมบูรณ์แบบ
.repeat(7, 1000)
กับ.repeat(2, 3500)
VS .repeat(35, 200
) ควรขึ้นอยู่กับวิธีการที่ผิดพลาดเนื่องจากการโหลดระบบจะเปรียบเทียบกับข้อผิดพลาดอันเนื่องมาจากความแปรปรวนของการป้อนข้อมูล ในกรณีที่รุนแรงหากระบบของคุณมีภาระงานหนักอยู่เสมอและคุณเห็นหางยาวที่ด้านซ้ายของการกระจายเวลาดำเนินการ (เมื่อคุณจับมันในสถานะที่ไม่ได้ใช้งานหายาก) คุณอาจพบว่า.repeat(7000,1)
มีประโยชน์มากกว่า.repeat(7,1000)
ถ้าคุณ ไม่สามารถทำงบประมาณได้มากกว่า 7000 ครั้ง
หากคุณต้องการใช้timeit
ในเซสชัน Python แบบโต้ตอบมีสองตัวเลือกที่สะดวก:
ใช้เปลือกIPython มันมี%timeit
ฟังก์ชั่นพิเศษที่สะดวกสบาย:
In [1]: def f(x):
...: return x*x
...:
In [2]: %timeit for x in range(100): f(x)
100000 loops, best of 3: 20.3 us per loop
ในล่าม Python มาตรฐานคุณสามารถเข้าถึงฟังก์ชั่นและชื่ออื่น ๆ ที่คุณกำหนดไว้ก่อนหน้านี้ในระหว่างเซสชันการโต้ตอบโดยการนำเข้าจาก__main__
ในคำสั่งตั้งค่า:
>>> def f(x):
... return x * x
...
>>> import timeit
>>> timeit.repeat("for x in range(100): f(x)", "from __main__ import f",
number=100000)
[2.0640320777893066, 2.0876040458679199, 2.0520210266113281]
from __main__ import f
เทคนิค ฉันไม่คิดว่ามันจะเป็นที่รู้จักอย่างกว้างขวางเท่าที่ควร มันจะมีประโยชน์ในกรณีเช่นนี้ที่มีการเรียกใช้ฟังก์ชันหรือเมธอด ในกรณีอื่น ๆ (จับเวลาเป็นลำดับขั้นตอน) จะมีประโยชน์น้อยกว่าเพราะแนะนำค่าใช้จ่ายในการเรียกใช้ฟังก์ชัน
%timeit f(x)
sys._getframe(N).f_globals
) ควรเป็นค่าเริ่มต้นตั้งแต่ต้น
ฉันจะให้คุณเป็นความลับ: วิธีที่ดีที่สุดที่จะใช้timeit
ในบรรทัดคำสั่ง
บนบรรทัดคำสั่งtimeit
ทำการวิเคราะห์ทางสถิติที่เหมาะสม: มันบอกคุณว่าการรันที่สั้นที่สุดใช้เวลานานเท่าใด นี่เป็นสิ่งที่ดีเพราะข้อผิดพลาดทั้งหมดในการกำหนดเวลาเป็นค่าบวก ดังนั้นเวลาที่สั้นที่สุดจึงมีข้อผิดพลาดน้อยที่สุด ไม่มีวิธีใดที่จะได้รับข้อผิดพลาดเชิงลบเพราะคอมพิวเตอร์ไม่สามารถคำนวณได้เร็วกว่าที่คำนวณได้!
ดังนั้นอินเตอร์เฟสบรรทัดคำสั่ง:
%~> python -m timeit "1 + 2"
10000000 loops, best of 3: 0.0468 usec per loop
มันค่อนข้างง่ายใช่มั้ย
คุณสามารถตั้งค่า:
%~> python -m timeit -s "x = range(10000)" "sum(x)"
1000 loops, best of 3: 543 usec per loop
ซึ่งมีประโยชน์เช่นกัน!
หากคุณต้องการหลายบรรทัดคุณสามารถใช้การต่อเนื่องอัตโนมัติของเชลล์หรือใช้อาร์กิวเมนต์แยกกัน:
%~> python -m timeit -s "x = range(10000)" -s "y = range(100)" "sum(x)" "min(y)"
1000 loops, best of 3: 554 usec per loop
ที่ให้การตั้งค่า
x = range(1000)
y = range(100)
และเวลา
sum(x)
min(y)
หากคุณต้องการมีสคริปต์ที่ยาวกว่าคุณอาจถูกล่อลวงให้ย้ายไปtimeit
อยู่ในสคริปต์ Python ฉันขอแนะนำให้หลีกเลี่ยงเพราะการวิเคราะห์และการกำหนดเวลานั้นทำได้ดีกว่าบนบรรทัดคำสั่ง แต่ฉันมักจะสร้างเชลล์สคริปต์:
SETUP="
... # lots of stuff
"
echo Minmod arr1
python -m timeit -s "$SETUP" "Minmod(arr1)"
echo pure_minmod arr1
python -m timeit -s "$SETUP" "pure_minmod(arr1)"
echo better_minmod arr1
python -m timeit -s "$SETUP" "better_minmod(arr1)"
... etc
การดำเนินการนี้อาจใช้เวลานานขึ้นเล็กน้อยเนื่องจากการกำหนดค่าเริ่มต้นหลายครั้ง แต่โดยทั่วไปไม่ใช่เรื่องใหญ่
แต่ถ้าคุณต้องการใช้timeit
ภายในโมดูลของคุณ
วิธีง่าย ๆ คือทำ:
def function(...):
...
timeit.Timer(function).timeit(number=NUMBER)
และนั่นจะช่วยให้คุณมีเวลาสะสม ( ไม่ต่ำสุด!) ในการรันจำนวนครั้งนั้น
หากต้องการวิเคราะห์ที่ดีให้ใช้.repeat
และทำขั้นต่ำ:
min(timeit.Timer(function).repeat(repeat=REPEATS, number=NUMBER))
โดยปกติคุณควรรวมสิ่งนี้เข้ากับfunctools.partial
แทนที่จะlambda: ...
ใช้เหนือศีรษะ ดังนั้นคุณสามารถมีสิ่งที่ชอบ:
from functools import partial
def to_time(items):
...
test_items = [1, 2, 3] * 100
times = timeit.Timer(partial(to_time, test_items)).repeat(3, 1000)
# Divide by the number of repeats
time_taken = min(times) / 1000
คุณยังสามารถทำสิ่งต่อไปนี้
timeit.timeit("...", setup="from __main__ import ...", number=NUMBER)
ซึ่งจะทำให้คุณได้ใกล้กับอินเตอร์เฟสมากขึ้นจาก command-line แต่ในลักษณะที่ดูเท่ห์กว่ามาก ช่วยให้คุณใช้รหัสจากโมดูลหลักของคุณภายในสภาพแวดล้อมเทียมที่สร้างขึ้นโดย"from __main__ import ..."
timeit
เป็นที่น่าสังเกตว่านี่เป็นเสื้อคลุมที่ใช้สะดวกTimer(...).timeit(...)
และไม่เหมาะกับเวลา โดยส่วนตัวแล้วฉันชอบที่จะใช้Timer(...).repeat(...)
ตามที่แสดงไว้ด้านบน
มีข้อแม้บางอย่างtimeit
ที่เก็บได้ทุกที่
ค่าโสหุ้ยไม่ถือเป็น สมมติว่าคุณต้องการเวลาx += 1
เพื่อดูว่าต้องใช้เวลานานเท่าใด:
>>> python -m timeit -s "x = 0" "x += 1"
10000000 loops, best of 3: 0.0476 usec per loop
ดีก็ไม่ 0.0476 ไมโครวินาที คุณจะรู้ว่ามันน้อยกว่านั้น ข้อผิดพลาดทั้งหมดเป็นค่าบวก
ดังนั้นลองค้นหาค่าโสหุ้ยที่บริสุทธิ์ :
>>> python -m timeit -s "x = 0" ""
100000000 loops, best of 3: 0.014 usec per loop
นั่นเป็นค่าใช้จ่ายที่ดี30%จากเวลา! สิ่งนี้สามารถทำให้การกำหนดเวลาแบบสัมพัทธ์อย่างหนาแน่น แต่คุณใส่ใจเรื่องเวลาที่เพิ่มเท่านั้น การกำหนดเวลาx
ค้นหาเพื่อรวมไว้ในค่าใช้จ่ายด้วย:
>>> python -m timeit -s "x = 0" "x"
100000000 loops, best of 3: 0.0166 usec per loop
ความแตกต่างนั้นไม่ใหญ่มาก แต่มันอยู่ที่นั่น
วิธีการกลายพันธุ์เป็นสิ่งที่อันตราย
>>> python -m timeit -s "x = [0]*100000" "while x: x.pop()"
10000000 loops, best of 3: 0.0436 usec per loop
แต่นั่นผิดทั้งหมด! x
เป็นรายการที่ว่างเปล่าหลังจากการวนซ้ำครั้งแรก คุณจะต้องเริ่มต้นใหม่:
>>> python -m timeit "x = [0]*100000" "while x: x.pop()"
100 loops, best of 3: 9.79 msec per loop
แต่คุณมีค่าใช้จ่ายมากมาย บัญชีสำหรับสิ่งนั้นต่างหาก
>>> python -m timeit "x = [0]*100000"
1000 loops, best of 3: 261 usec per loop
โปรดทราบว่าการลบค่าโสหุ้ยนั้นสมเหตุสมผลที่นี่เพียงเพราะค่าใช้จ่ายเป็นส่วนเล็ก ๆ ของเวลา
สำหรับตัวอย่างของคุณควรสังเกตว่าทั้ง Insertion Sort และ Tim Sort มีพฤติกรรมการจับเวลาที่ผิดปกติอย่างสมบูรณ์สำหรับรายการที่เรียงลำดับแล้ว ซึ่งหมายความว่าคุณจะต้องใช้random.shuffle
ระหว่างการเรียงลำดับหากคุณต้องการหลีกเลี่ยงการทำลายเวลาของคุณ
timeit
จากโปรแกรม แต่ทำงานในลักษณะเดียวกับบรรทัดคำสั่งหรือไม่ .
timeit
ดำเนินการpass
คำสั่งเมื่อไม่มีข้อโต้แย้งที่จะได้รับซึ่งแน่นอนใช้เวลา หากมีการให้ข้อโต้แย้งใด ๆpass
จะไม่ได้รับการดำเนินการดังนั้นการลบ0.014
usecs บางตัวจากทุกจังหวะจะไม่ถูกต้อง
หากคุณต้องการเปรียบเทียบโค้ด / ฟังก์ชั่นสองบล็อกอย่างรวดเร็วคุณสามารถทำได้:
import timeit
start_time = timeit.default_timer()
func1()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
func2()
print(timeit.default_timer() - start_time)
ฉันพบวิธีที่ง่ายที่สุดในการใช้ timeit คือจากบรรทัดคำสั่ง:
รับtest.py :
def InsertionSort(): ...
def TimSort(): ...
เรียกใช้ timeit แบบนี้:
% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'
สำหรับฉันนี่เป็นวิธีที่เร็วที่สุด:
import timeit
def foo():
print("here is my code to time...")
timeit.timeit(stmt=foo, number=1234567)
# Генерация целых чисел
def gen_prime(x):
multiples = []
results = []
for i in range(2, x+1):
if i not in multiples:
results.append(i)
for j in range(i*i, x+1, i):
multiples.append(j)
return results
import timeit
# Засекаем время
start_time = timeit.default_timer()
gen_prime(3000)
print(timeit.default_timer() - start_time)
# start_time = timeit.default_timer()
# gen_prime(1001)
# print(timeit.default_timer() - start_time)
วิธีนี้ใช้งานได้ดี:
python -m timeit -c "$(cat file_name.py)"
ให้ตั้งค่าพจนานุกรมเดียวกันในแต่ละรายการต่อไปนี้และทดสอบเวลาดำเนินการ
อาร์กิวเมนต์การตั้งค่าเป็นพื้นฐานการตั้งค่าพจนานุกรม
Number คือการเรียกใช้รหัส 1000000 ครั้ง ไม่ใช่การตั้งค่า แต่เป็นตัวจัดการ
เมื่อคุณรันสิ่งนี้คุณจะเห็นว่าดัชนีนั้นเร็วกว่าที่จะเป็นไปได้ คุณสามารถเรียกใช้ได้หลายครั้งเพื่อดู
รหัสโดยทั่วไปพยายามรับค่า c ในพจนานุกรม
import timeit
print('Getting value of C by index:', timeit.timeit(stmt="mydict['c']", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))
print('Getting value of C by get:', timeit.timeit(stmt="mydict.get('c')", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))
นี่คือผลลัพธ์ของฉันคุณจะแตกต่างกัน
โดยดัชนี: 0.20900007452246427
โดยรับ: 0.54841166886888
เพียงส่งรหัสทั้งหมดของคุณเป็นอาร์กิวเมนต์ของเวลา:
import timeit
print(timeit.timeit(
"""
limit = 10000
prime_list = [i for i in range(2, limit+1)]
for prime in prime_list:
for elem in range(prime*2, max(prime_list)+1, prime):
if elem in prime_list:
prime_list.remove(elem)
"""
, number=10))
import timeit
def oct(x):
return x*x
timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()
gc.enable()
อะไร
โมดูล timeit ในตัวทำงานได้ดีที่สุดจากบรรทัดคำสั่ง IPython
ถึงฟังก์ชั่นเวลาจากภายในโมดูล:
from timeit import default_timer as timer
import sys
def timefunc(func, *args, **kwargs):
"""Time a function.
args:
iterations=3
Usage example:
timeit(myfunc, 1, b=2)
"""
try:
iterations = kwargs.pop('iterations')
except KeyError:
iterations = 3
elapsed = sys.maxsize
for _ in range(iterations):
start = timer()
result = func(*args, **kwargs)
elapsed = min(timer() - start, elapsed)
print(('Best of {} {}(): {:.9f}'.format(iterations, func.__name__, elapsed)))
return result
ตัวอย่างวิธีใช้ Python REPL interpreter กับฟังก์ชั่นที่รับพารามิเตอร์
>>> import timeit
>>> def naive_func(x):
... a = 0
... for i in range(a):
... a += i
... return a
>>> def wrapper(func, *args, **kwargs):
... def wrapper():
... return func(*args, **kwargs)
... return wrapper
>>> wrapped = wrapper(naive_func, 1_000)
>>> timeit.timeit(wrapped, number=1_000_000)
0.4458435332577161
คุณจะสร้างสองฟังก์ชั่นแล้วเรียกใช้บางอย่างที่คล้ายกัน แจ้งให้ทราบล่วงหน้าคุณต้องการเลือกจำนวนการดำเนินการ / เรียกใช้เพื่อเปรียบเทียบแอปเปิ้ลกับแอปเปิ้ล
สิ่งนี้ถูกทดสอบภายใต้ Python 3.7
นี่คือรหัสเพื่อความสะดวกในการคัดลอก
!/usr/local/bin/python3
import timeit
def fibonacci(n):
"""
Returns the n-th Fibonacci number.
"""
if(n == 0):
result = 0
elif(n == 1):
result = 1
else:
result = fibonacci(n-1) + fibonacci(n-2)
return result
if __name__ == '__main__':
import timeit
t1 = timeit.Timer("fibonacci(13)", "from __main__ import fibonacci")
print("fibonacci ran:",t1.timeit(number=1000), "milliseconds")
timsort(a)
และใช้ความแตกต่าง :-)