วิธีใช้โมดูล timeit


351

ฉันเข้าใจแนวคิดของสิ่งที่timeitทำ แต่ฉันไม่แน่ใจว่าจะนำไปใช้ในโค้ดของฉันได้อย่างไร

ฉันจะเปรียบเทียบทั้งสองฟังก์ชันพูดinsertion_sortและtim_sortกับได้timeitอย่างไร

คำตอบ:


266

วิธี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 อย่างถูกต้อง หวังว่านี่จะช่วย :-)


8
ใช่มันรวมถึงการคัดลอกรายการ (ซึ่งเร็วมากเมื่อเทียบกับการเรียงลำดับตัวเอง) หากคุณไม่ได้คัดลอกรหัสผ่านแรกจะเรียงลำดับรายการและรหัสผ่านที่เหลือไม่ต้องทำงานใด ๆ หากคุณต้องการทราบเวลาเพียงสำหรับการจัดเรียงแล้วเรียกใช้ข้างต้นด้วยและไม่มีtimsort(a)และใช้ความแตกต่าง :-)
Raymond Hettinger

ฉันขอแนะนำให้ทำซ้ำ 7 ครั้งสำหรับการตั้งค่าแต่ละครั้งและโดยเฉลี่ย มากกว่าทางกลับกัน ด้วยวิธีนี้หากเข็มแต่ละอันเนื่องมาจากกระบวนการอื่นมีโอกาสที่จะถูกเพิกเฉยโดยสิ้นเชิงแทนที่จะเฉลี่ย
สูงสุด

75
@max ใช้ min () มากกว่าค่าเฉลี่ยของการกำหนดเวลา นั่นคือคำแนะนำจากฉันจาก Tim Peters และจาก Guido van Rossum เวลาที่เร็วที่สุดแสดงให้เห็นว่าอัลกอริทึมที่ดีที่สุดสามารถทำงานได้เมื่อโหลดแคชและระบบไม่ยุ่งกับงานอื่น ๆ เวลาทั้งหมดนั้นมีเสียงดัง - เวลาที่เร็วที่สุดคือเสียงรบกวนน้อยที่สุด เป็นการง่ายที่จะแสดงว่าการกำหนดเวลาที่เร็วที่สุดนั้นจะทำซ้ำได้มากที่สุดและมีประโยชน์มากที่สุดเมื่อกำหนดเวลาการปรับใช้สองแบบ
Raymond Hettinger

4
คุณคำนวณค่าเฉลี่ย (ดี, รวม, แต่เทียบเท่ากัน) สำหรับ 1,000 อินพุต จากนั้นทำซ้ำ 7 ครั้งและใช้เวลาขั้นต่ำ คุณต้องการค่าเฉลี่ยมากกว่า 1,000 อินพุตเนื่องจากคุณต้องการความซับซ้อนของอัลกอริทึมเฉลี่ย (ไม่ใช่ตัวพิมพ์ที่ดีที่สุด) คุณต้องการขั้นต่ำสำหรับเหตุผลที่คุณให้ ฉันคิดว่าฉันสามารถปรับปรุงวิธีการของคุณโดยเลือกหนึ่งอินพุทใช้อัลกอริทึม 7 ครั้งลดขั้นต่ำ จากนั้นทำซ้ำเป็น 1,000 อินพุตที่แตกต่างกันและรับค่าเฉลี่ย สิ่งที่ฉันไม่ได้ตระหนักคือคุณ.repeat(7,1000)ทำสิ่งนี้อยู่แล้ว (โดยใช้เมล็ดพันธุ์เดียวกัน)! ดังนั้นทางออกของคุณคือ IMO ที่สมบูรณ์แบบ
สูงสุด

5
ฉันสามารถเพิ่มที่วิธีการที่คุณจัดสรรงบประมาณ 7000 ประหารชีวิต (เช่น.repeat(7, 1000)กับ.repeat(2, 3500)VS .repeat(35, 200) ควรขึ้นอยู่กับวิธีการที่ผิดพลาดเนื่องจากการโหลดระบบจะเปรียบเทียบกับข้อผิดพลาดอันเนื่องมาจากความแปรปรวนของการป้อนข้อมูล ในกรณีที่รุนแรงหากระบบของคุณมีภาระงานหนักอยู่เสมอและคุณเห็นหางยาวที่ด้านซ้ายของการกระจายเวลาดำเนินการ (เมื่อคุณจับมันในสถานะที่ไม่ได้ใช้งานหายาก) คุณอาจพบว่า.repeat(7000,1)มีประโยชน์มากกว่า.repeat(7,1000)ถ้าคุณ ไม่สามารถทำงบประมาณได้มากกว่า 7000 ครั้ง
สูงสุด

277

หากคุณต้องการใช้timeitในเซสชัน Python แบบโต้ตอบมีสองตัวเลือกที่สะดวก:

  1. ใช้เปลือก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
  2. ในล่าม 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]

97
+1 สำหรับแสดงfrom __main__ import fเทคนิค ฉันไม่คิดว่ามันจะเป็นที่รู้จักอย่างกว้างขวางเท่าที่ควร มันจะมีประโยชน์ในกรณีเช่นนี้ที่มีการเรียกใช้ฟังก์ชันหรือเมธอด ในกรณีอื่น ๆ (จับเวลาเป็นลำดับขั้นตอน) จะมีประโยชน์น้อยกว่าเพราะแนะนำค่าใช้จ่ายในการเรียกใช้ฟังก์ชัน
Raymond Hettinger

15
คุณก็สามารถทำ%timeit f(x)
QED

หมายเหตุ: การตั้งค่า "นำเข้า f" ทำให้การเข้าถึงการอ่านอย่างรวดเร็วภายในเครื่อง - ซึ่งไม่เหมือนกับการเรียกใช้ฟังก์ชันทั่วโลก (ของฟังก์ชั่นเร็วเร็ว) ในรหัสปกติทั่วไป ใน Py3.5 + globals จริงสามารถจัดจำหน่าย: "เปลี่ยนเป็นเวอร์ชั่น 3.5: เพิ่มพารามิเตอร์ globals เพิ่มเติม"; ก่อนหน้ากลมของโมดูล timeit ที่หลีกเลี่ยงไม่ได้ (ซึ่งไม่ค่อยสมเหตุสมผล) อาจเป็นไปได้ว่ารหัสวงกลมโทร ( sys._getframe(N).f_globals) ควรเป็นค่าเริ่มต้นตั้งแต่ต้น
kxr

140

ฉันจะให้คุณเป็นความลับ: วิธีที่ดีที่สุดที่จะใช้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ระหว่างการเรียงลำดับหากคุณต้องการหลีกเลี่ยงการทำลายเวลาของคุณ


1
usec หมายถึงอะไร มันเป็นไมโครวินาทีหรือไม่
Hasan Iqbal

2
@HasanIqbalAnik ใช่
Veedrac

@StefanPochmann เพราะมันไม่ได้พยายามสุ่มตัวอย่างหลายครั้ง
Veedrac


@Veedrac พิจารณาคำสั่งของคุณเกี่ยวกับการลบค่าใช้จ่ายในช่วงเวลาบริสุทธิ์timeitดำเนินการpassคำสั่งเมื่อไม่มีข้อโต้แย้งที่จะได้รับซึ่งแน่นอนใช้เวลา หากมีการให้ข้อโต้แย้งใด ๆpassจะไม่ได้รับการดำเนินการดังนั้นการลบ0.014usecs บางตัวจากทุกจังหวะจะไม่ถูกต้อง
Arne

99

หากคุณต้องการเปรียบเทียบโค้ด / ฟังก์ชั่นสองบล็อกอย่างรวดเร็วคุณสามารถทำได้:

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)

43

ฉันพบวิธีที่ง่ายที่สุดในการใช้ timeit คือจากบรรทัดคำสั่ง:

รับtest.py :

def InsertionSort(): ...
def TimSort(): ...

เรียกใช้ timeit แบบนี้:

% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'


12
# Генерация целых чисел

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)

7

วิธีนี้ใช้งานได้ดี:

  python -m timeit -c "$(cat file_name.py)"

สิ่งที่เทียบเท่ากับ Windows
Shailen

2
คุณจะส่งผ่านพารามิเตอร์ได้อย่างไรถ้าสคริปต์ต้องการใด ๆ
Juuso Ohtonen

3

ให้ตั้งค่าพจนานุกรมเดียวกันในแต่ละรายการต่อไปนี้และทดสอบเวลาดำเนินการ

อาร์กิวเมนต์การตั้งค่าเป็นพื้นฐานการตั้งค่าพจนานุกรม

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


คุณใช้ไพ ธ อนรุ่นใด
Eduardo

3

เพียงส่งรหัสทั้งหมดของคุณเป็นอาร์กิวเมนต์ของเวลา:

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


0

โมดูล 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

0

ตัวอย่างวิธีใช้ 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                                                                                        

0

คุณจะสร้างสองฟังก์ชั่นแล้วเรียกใช้บางอย่างที่คล้ายกัน แจ้งให้ทราบล่วงหน้าคุณต้องการเลือกจำนวนการดำเนินการ / เรียกใช้เพื่อเปรียบเทียบแอปเปิ้ลกับแอปเปิ้ล
สิ่งนี้ถูกทดสอบภายใต้ 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")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.