-n和-r参与IPython' s%timeit magic

时间:2018-01-15 06:35:55

标签: python ipython jupyter

我想在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重复将处理任何系统可变性。

2 个答案:

答案 0 :(得分:1)

%timeit的最新版本看起来取r n-loop平均值的平均值,而不是平均值的最佳值。

enter image description here

显然,这已经从早期版本的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毫秒:

enter image description here

在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毫秒以下的急剧截止,这表明这是可以执行操作的“最佳”时间。较高的计时是测量,条件不是最佳条件,或者其他进程/线程花费了一些时间:

enter image description here

避免这些波动的典型方法是非常频繁地重复次数,然后使用统计信息获得最准确的数字。哪个统计取决于您要测量的内容。我将在下面对此进行更详细的介绍。

同时使用 number repeat

实质上,%timeittimeit.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)

但是%timeittimeit.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 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()

enter image description here

与该“建议”相反(请参阅上面引用的文档),IPython %timeit报告平均值而不是min()。但是默认情况下,他们也只使用 repeat 为7-我认为准确地确定 minimum 太少了-因此在这种情况下使用平均值实际上是明智的。做“快速而肮脏的”计时的好工具。

如果您需要一些可以根据自己的需求进行自定义的内容,则可以直接使用timeit.repeat甚至是第三方模块。例如: