我想在Jupyter笔记本中使用timeit
magic命令对代码块进行计时。根据文档,timeit
有几个论点。两个特别是控制循环次数和重复次数。我不清楚这两个论点之间的区别。例如
import numpy
N = 1000000
v = numpy.arange(N)
%timeit -n 10 -r 500 pass; w = v + v
将运行10次循环和500次重复。我的问题是,
这可以解释为 以下? (实际时间结果明显不同)
import time
n = 10
r = 500
T = numpy.empty(r)
for j in range(r):
t0 = time.time()
for i in range(n):
w = v + v
T[j] = (time.time() - t0)/n
print('Best time is {:.4f} ms'.format(max(T)*1000))
我正在做的一个假设,可能是不正确的,内循环的时间是通过这个循环在n
迭代上的平均值。然后进行500循环的最佳循环。
我搜索了文档,但没有找到任何可以确切说明这一点的内容。例如,文档here是
选项:-n:在循环中执行给定的语句次数。如果未给出该值,则选择拟合值。
-r:重复循环迭代次数并获得最佳结果。默认值:3
关于内循环如何计时,没有真正说过。最终的结果是"最好的"什么?
我想要的时间代码不涉及任何随机性,所以我想知道是否应该将此内部循环设置为n=1
。然后,r
重复将处理任何系统可变性。
答案 0 :(得分:1)
%timeit
的最新版本看起来取r
n-loop平均值的平均值,而不是平均值的最佳值。
显然,这已经从早期版本的Python发生了变化。 r
返回参数仍然可以获得TimeResults
平均值的最佳时间,但它不再是显示的值。
答案 1 :(得分:0)
number 和 repeat 是单独的参数,是因为它们具有不同的用途。 number 控制每个计时执行多少次执行,并用于获取代表性计时。 repeat 参数控制执行多少个计时,其用途是获取准确的统计信息。 IPython使用 mean 或 average 来计算所有重复语句的运行时间,然后将该数字除以 number 。因此,它测量平均值的平均值。在早期版本中,它使用所有重复的最短时间(min()
),然后将其除以 number ,并报告为“最佳”。
要了解为什么有两个参数来控制 number 和 repeats ,您必须了解自己的时间安排以及如何测量时间。
计算机具有不同的“时钟”来测量时间。这些时钟具有不同的“滴答声”(取决于操作系统)。例如,它可以测量秒,毫秒或纳秒-这些刻度称为时钟的粒度。
如果执行的持续时间小于或大致等于时钟的粒度,则无法获得代表性的时间。假设您的操作将花费100ns(= 0.0000001秒),但时钟仅测量毫秒(= 0.001秒),则大多数测量将测量0毫秒,而少数测量将测量1毫秒-这取决于执行在时钟周期中的哪个位置开始执行,以及完成。这并不能真正代表您想要的时间长短。
这是在time.time
的粒度为1毫秒的Windows上:
import time
def fast_function():
return None
r = []
for _ in range(10000):
start = time.time()
fast_function()
r.append(time.time() - start)
import matplotlib.pyplot as plt
plt.title('measuring time of no-op-function with time.time')
plt.ylabel('number of measurements')
plt.xlabel('measured time [s]')
plt.yscale('log')
plt.hist(r, bins='auto')
plt.tight_layout()
这显示了此示例中测量时间的直方图。几乎所有测量均为0毫秒,而三个测量均为1毫秒:
在Windows上有些时钟的粒度要低得多,这只是为了说明粒度的影响,即使每个时钟都小于1毫秒,也具有一定的粒度。
要克服粒度的限制,可以增加执行次数,因此预期的持续时间显着高于时钟的粒度。因此,执行一次 number 次后就不必再运行执行了。从上方获取数字并使用数字为100 000,预期运行时间将为= 0.01秒。因此,忽略所有其他内容,现在几乎在所有情况下时钟都将测量为10毫秒,这将准确地类似于预期的执行时间。
简而言之,指定 number 可以测量 number 执行的 sum 。您需要再次用这种方法将时间度量除以 number 以获得“每次执行时间”。
您的操作系统通常具有许多活动进程,其中一些可以并行运行(不同的处理器或使用超线程),但是大多数操作系统按操作系统调度时间顺序运行,以便每个进程在CPU上运行。大多数时钟都不在乎当前运行什么进程,因此根据计划计划,测量的时间会有所不同。还有一些时钟可以代替测量系统时间来测量过程时间。但是,它们衡量的是Python进程的完整时间,该进程有时会包含垃圾回收或其他Python线程-除了Python进程不是无状态的,并且并非每个操作都总是完全相同的,并且还有内存分配/重新分配/清除发生(有时在幕后),并且这些内存操作时间可能会因很多原因而有所不同。
同样,我使用直方图来测量在计算机上求和一万个数字所花费的时间(仅使用 repeat 并将 number 设置为1):
import timeit
r = timeit.repeat('sum(1 for _ in range(10000))', number=1, repeat=1_000)
import matplotlib.pyplot as plt
plt.title('measuring summation of 10_000 1s')
plt.ylabel('number of measurements')
plt.xlabel('measured time [s]')
plt.yscale('log')
plt.hist(r, bins='auto')
plt.tight_layout()
此直方图显示在大约5毫秒以下的急剧截止,这表明这是可以执行操作的“最佳”时间。较高的计时是测量,条件不是最佳条件,或者其他进程/线程花费了一些时间:
避免这些波动的典型方法是非常频繁地重复次数,然后使用统计信息获得最准确的数字。哪个统计取决于您要测量的内容。我将在下面对此进行更详细的介绍。
实质上,%timeit
是timeit.repeat
的包装,大致相当于:
import timeit
timer = timeit.default_timer()
results = []
for _ in range(repeat):
start = timer()
for _ in range(number):
function_or_statement_to_time
results.append(timer() - start)
但是%timeit
与timeit.repeat
相比具有一些便利功能。例如,它根据 repeat 和 number 的计时来计算一个执行的最佳时间和平均时间。
这些大致是这样计算的:
import statistics
best = min(results) / number
average = statistics.mean(results) / number
您还可以使用TimeitResult
(如果使用-o
选项则返回)来检查所有结果:
>>> r = %timeit -o ...
7.46 ns ± 0.0788 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
>>> r.loops # the "number" is called "loops" on the result
100000000
>>> r.repeat
7
>>> r.all_runs
[0.7445439999999905,
0.7611092000000212,
0.7249667000000102,
0.7238135999999997,
0.7385598000000186,
0.7338551999999936,
0.7277425999999991]
>>> r.best
7.238135999999997e-09
>>> r.average
7.363701571428618e-09
>>> min(r.all_runs) / r.loops # calculated best by hand
7.238135999999997e-09
>>> from statistics import mean
>>> mean(r.all_runs) / r.loops # calculated average by hand
7.363701571428619e-09
如果要修改 number 或 repeat ,则应将 number 设置为可能的最小值,而不用担心计时器。根据我的经验,应该设置 number ,以便函数的 number 执行至少要花费10微秒(0.00001秒),否则您可能只是“时间”“计时器”。
重复应设置得尽可能高。重复次数越多,您越有可能真正找到真正的最佳或平均水平。但是,如果重复次数更多,则需要更长的时间,因此也需要进行权衡。
IPython调整 number ,但保持 repeat 不变。我经常做相反的事情:调整 number ,使语句的 number 执行花费约10us,然后调整得到的 repeat 统计数据的良好表示形式(通常在100-10000范围内)。但是您的里程可能会有所不同。
timeit.repeat
的文档中提到了这一点:
注意
试图根据结果向量计算均值和标准差并报告这些值。但是,这不是很有用。在典型情况下,最小值给出了机器可以运行给定代码段的速度的下限;结果向量中较高的值通常不是由Python速度的可变性引起的,而是由其他干扰您的计时精度的过程引起的。因此,结果的min()可能是您应该感兴趣的唯一数字。在那之后,您应该查看整个向量,并应用常识而不是统计学。
例如,一个人通常想找出算法有多快,然后就可以使用这些重复中的最小值。如果人们对计时的平均值或中位数更感兴趣,则可以使用这些度量。在大多数情况下,最感兴趣的数字是最小值,因为最小值类似于执行的速度-最小值可能是进程被中断最少(由其他进程,GC或中断得最多的一个执行)最佳内存操作)。
为说明差异,我再次重复了上述计时,但是这次我包括了最小值,均值和中位数:
import timeit
r = timeit.repeat('sum(1 for _ in range(10000))', number=1, repeat=1_000)
import numpy as np
import matplotlib.pyplot as plt
plt.title('measuring summation of 10_000 1s')
plt.ylabel('number of measurements')
plt.xlabel('measured time [s]')
plt.yscale('log')
plt.hist(r, bins='auto', color='black', label='measurements')
plt.tight_layout()
plt.axvline(np.min(r), c='lime', label='min')
plt.axvline(np.mean(r), c='red', label='mean')
plt.axvline(np.median(r), c='blue', label='median')
plt.legend()
与该“建议”相反(请参阅上面引用的文档),IPython %timeit
报告平均值而不是min()
。但是默认情况下,他们也只使用 repeat 为7-我认为准确地确定 minimum 太少了-因此在这种情况下使用平均值实际上是明智的。做“快速而肮脏的”计时的好工具。
如果您需要一些可以根据自己的需求进行自定义的内容,则可以直接使用timeit.repeat
甚至是第三方模块。例如:
pyperf
perfplot
simple_benchmark
(我自己的图书馆)