MATLAB 的timeit 执行以下操作(您可以阅读整个函数,它是一个 M 文件):
- 粗略估计
t_rough调用函数f的时间。
- 使用估计值确定
N,使得N*t_rough 约为0.001 秒。
- 确定
M 使得M*N*t_rough 不超过15 秒,但M 必须介于3 和11 之间。
- 循环
M次:
- 拨打
f()N次并记录总时间。
- 确定
M 次的中位数除以N。
M和N这两个循环的目的如下:调用f()Ntimes确保tic/toc测量的时间足够大是可靠的,这个循环避免了尝试对时间太短以至于无法计时的事情进行计时。重复测量M 次并保持中值尝试使测量对系统上发生的其他事情造成的延迟具有稳健性,这可能会人为地夸大记录的时间。
该函数通过其句柄减去调用函数的开销(通过对空函数的调用计时来实验确定),以及tic/toc 调用时间(也通过实验确定)。它并没有减去内循环的成本,大概是因为在 MATLAB 中它是由 JIT 优化的,它的成本可以忽略不计。
还有一些进一步的改进。确定t_rough 的函数首先通过两次调用tic 和toc,然后使用while 循环确保调用f() 至少0.001 秒。但是在这个循环中,如果第一次迭代至少需要 3 秒,那么它只是将那个时间作为粗略估计。如果第一次迭代耗时较少,则丢弃第一次计数(预热),然后使用所有后续调用的中位数作为时间的粗略估计。
使用正确数量的输出参数调用函数 f() 也付出了很多努力。
代码中有很多cmets解释了所有这些步骤背后的原因,值得一读。
至少,我会按如下方式扩充您的基准测试功能:
function elapsed_time_in_seconds = benchmark(f, N, M)
% benchmark runs the function 'f' N*M times and returns the elapsed time in seconds.
tic; [~] = toc; tic; [~] = toc; % warmup
output = f(); % warmup
t = zeros(M, 1);
for k=1:M
timeid = tic;
for i=1:N
output = f();
end
t(k) = toc(timeid) / N;
end
elapsed_time_in_seconds = median(t);
end
如果使用函数直接比较各种备选方案,保持N和M不变,那么tic、toc、函数调用和循环的开销就无关紧要了。
此函数确实假设f 有一个输出参数,但不一定如此。您可以只调用f() 而不是output = f(),它适用于有或没有输出参数的函数。但是,如果函数需要一定数量的输出才能正常工作,或者触发您想要计时的计算,那么您必须调整函数以使用正确数量的输出参数调用它。
您可以想出一些启发式方法来从N 中确定M,这样会更容易使用此函数。