Don*_*nna 19 python ipython jupyter
我想timeit在 Jupyter 笔记本中使用魔法命令对代码块计时。根据文档,timeit需要几个参数。两个特别控制循环次数和重复次数。我不清楚的是这两个论点之间的区别。例如
import numpy
N = 1000000
v = numpy.arange(N)
%timeit -n 10 -r 500 pass; w = v + v
Run Code Online (Sandbox Code Playgroud)
将运行 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))
Run Code Online (Sandbox Code Playgroud)
我所做的一个假设很可能是不正确的,即内循环的时间是平均的 n通过这个循环迭代的。然后采用该循环的 500 次重复中最好的一次。
我搜索了文档,但没有找到任何可以准确说明这是做什么的。例如,这里的文档是
选项: -n:在循环中执行给定的语句次数。如果未给出此值,则选择一个拟合值。
-r:重复循环迭代次数并取最好的结果。默认值:3
关于内部循环是如何计时的,并没有真正说过。最终的结果是“最好的”什么?
我想要计时的代码不涉及任何随机性,所以我想知道是否应该将此内部循环设置为n=1. 然后,r重复将处理任何系统可变性。
MSe*_*ert 14
该数字和重复是不同的参数,因为它们用于不同的目的。该数字控制每个计时完成的执行次数,它用于获取代表性计时。该重复参数控制多少定时完成,其用途是获得准确的统计数据。IPython 使用平均值或平均值来计算所有重复语句的运行时间,然后将该数字除以number。所以它测量平均值的平均值。在早期版本中,它使用min()所有重复的最短时间 ( )并将其除以数字并将其报告为“最佳”。
要了解为什么有两个参数来控制数量和重复次数,您必须了解您的计时以及如何测量时间。
计算机有不同的“时钟”来测量时间。这些时钟有不同的“滴答声”(取决于操作系统)。例如,它可以测量秒、毫秒或纳秒——这些滴答声被称为时钟的粒度。
如果执行的持续时间小于或大致等于时钟的粒度,则无法获得具有代表性的时序。假设您的操作将花费 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()
Run Code Online (Sandbox Code Playgroud)
这显示了此示例中测量时间的直方图。几乎所有的测量值都是 0 毫秒,三个测量值都是 1 毫秒:
Windows 上有些时钟的粒度要低得多,这只是为了说明粒度的影响,每个时钟即使低于一毫秒也有一定的粒度。
为了克服粒度的限制,可以增加执行次数,使预期的持续时间明显高于时钟的粒度。因此,而不是运行执行,一旦它的投放数量倍。从上面获取数字并使用100 000 的数字,预期运行时间将为 = 0.01 秒。因此,忽略其他一切,时钟现在几乎在所有情况下都会测量 10 毫秒,这将准确地类似于预期的执行时间。
总之指定数量的措施的总和的数执行。您需要再次将这种方式测量的时间除以数字以获得“每次执行时间”。
您的操作系统通常有许多活动进程,其中一些可以并行运行(不同的处理器或使用超线程),但其中大多数按操作系统的调度时间顺序运行,以便每个进程在 CPU 上运行。大多数时钟不关心当前正在运行什么进程,因此测量的时间会根据调度计划而有所不同。还有一些时钟不是测量系统时间而是测量过程时间。然而,它们测量 Python 进程的完整时间,有时会包括垃圾收集或其他 Python 线程——此外,Python 进程不是无状态的,并非每个操作总是完全相同,
我再次使用直方图测量在我的计算机上求和一万个所需的时间(仅使用重复并将数字设置为 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()
Run Code Online (Sandbox Code Playgroud)
此直方图显示在约 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)
Run Code Online (Sandbox Code Playgroud)
但%timeit与timeit.repeat. 例如,它根据通过repeat和number获得的时间计算一次执行的最佳时间和平均时间。
这些计算大致如下:
import statistics
best = min(results) / number
average = statistics.mean(results) / number
Run Code Online (Sandbox Code Playgroud)
您还可以使用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
Run Code Online (Sandbox Code Playgroud)
如果要修改数字或重复,则应将数字设置为可能的最小值,而不会影响计时器的粒度。根据我的经验,数字应该设置为使函数的数字执行至少需要 10 微秒(0.00001 秒),否则您可能只会“计时”“计时器”的最小分辨率。
该重复应设置尽可能高。重复次数越多,您就越有可能真正找到真正的最佳或平均水平。然而,更多的重复需要更长的时间,因此也需要权衡。
IPython 调整数字但保持重复不变。我经常做相反的事情:我调整数字,以便语句的执行次数需要 ~10us,然后我调整重复,以获得良好的统计数据表示(通常在 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()
Run Code Online (Sandbox Code Playgroud)
与这个“建议”相反(参见上面引用的文档),IPythons%timeit报告的是平均值而不是min(). 然而,默认情况下,他们也只使用7的重复- 我认为这太少了,无法准确确定最小值- 所以在这种情况下使用平均值实际上是明智的。这是一个很好的工具,可以进行“快速而肮脏”的计时.
如果您需要允许根据您的需要对其进行自定义的东西,则可以timeit.repeat直接使用,甚至可以使用3rd 方模块。例如:
pyperfperfplotsimple_benchmark (我自己的图书馆)看起来最新版本%timeit是取rn-loop 平均值的平均值,而不是最好的平均值。
显然,这与早期版本的 Python 有所不同。r仍然可以通过TimeResults返回参数获得平均的最佳时间,但它不再是显示的值。
评论:我最近从上面运行了这段代码,发现以下语法不再有效:
n = 1
r = 50
tr = %timeit -n $n -r $r -q -o pass; compute_mean(x,np)
Run Code Online (Sandbox Code Playgroud)
也就是说,不再可能(似乎)$var用于将变量传递给timeit魔术命令。这是否意味着这个魔术命令应该被淘汰并替换为timeit模块?
我正在使用 Python 3.7.4。