我有这个 C89 小程序——它的作用、工作原理以及未定义的行为并不重要,只是它仅在一个线程上执行一些长内存和数学运算:
x, c, * b, m, t, i, a;
g (n) {
for (b = malloc(0); c < n; b[c - 1] = x++, t = 1) {
char s[9];
for (i = m = 0; i < sprintf(s, "%d", x); m += a, t *= a) a = s[i++] - 48;
b = m * t ? x % m + x % t ? b : realloc(b, 4 * ++c) : b;
}
return b[c - 1];
}
main (j) {
printf("%d\n", g(--j));
}
像这样编译它:gcc -std=c89 tt.c -o tt -O3
。
然后,如果我使用 shell 脚本循环运行它,以了解其运行时间:
#!/bin/bash
echo "using input $1"
for _ in `seq 1 10`; do
( time ./tt $(seq 1 $1) ) 3>&1 1>/dev/null 2>&3 \
| grep real \
| cut -f2
# sleep 5
done
我看到这样的输出:
$ ./tt.sh 50
using input 50
0m0.016s
0m0.008s
0m0.008s
0m0.007s
0m0.007s
0m0.007s
0m0.008s
0m0.008s
0m0.007s
0m0.007s
或者像这样:
$ ./tt.sh 34
using input 34
0m0.007s
0m0.004s
0m0.004s
0m0.004s
0m0.005s
0m0.004s
0m0.003s
0m0.003s
0m0.003s
0m0.004s
第一次调用后,程序的运行时会出现初始加速real
,然后所有后续调用都以此假加速运行。
如果我取消# sleep 5
shell 脚本中该行的注释,我们会看到以下结果:
using input 50
0m0.008s
0m0.020s
0m0.018s
0m0.012s
0m0.009s
0m0.006s
0m0.013s
0m0.012s
0m0.009s
0m0.012s
using input 34
0m0.006s
0m0.007s
0m0.004s
0m0.007s
0m0.008s
0m0.003s
0m0.004s
0m0.004s
0m0.005s
0m0.007s
这些时间显得更加符合预期和准确,并且其中的差异必须归因于该时刻处理器的随机状态(即,它们是小的自然变化)。
如果我想获得程序的平均运行时间,我应该对这些数字进行平均,但是sleep 5
在每次调用之间,虽然这是我能找到阻止这种行为的唯一方法,但 10 次测试总共需要 50 秒,而不是几次秒进行 20 次测试。
我之前在单线程程序中见过这种“重播缓存”行为,这些程序一遍又一遍地执行长时间操作(紧密循环),并且我知道在 99.9% 的情况下这是可取的。
假设这不是硬件级别的某些 Intel Magic™ 的结果,这是 Linux 内核或 Bash 故意做的事情吗?我怎样才能阻止它?
我希望我的程序具有可重现的运行时,包括从“冷启动”加载库和分页,而不需要sleep 5
ing,因为受缓存影响的时间并不代表每次都是冷启动。
答案1
正如我的评论中所说:
可能不会有帮助,但在每次调用之前尝试“echo 3 > /proc/sys/vm/drop_caches”
tt
提问者的回答:
是的,回声 1 | sudo tee /proc/sys/vm/drop_caches 正是我想要的(回显 3 相反给出了一个奇怪的结果)。你应该把它作为答案
价值记录:
To free pagecache:
echo 1 > /proc/sys/vm/drop_caches
To free reclaimable slab objects (includes dentries and inodes):
echo 2 > /proc/sys/vm/drop_caches
To free slab objects and pagecache:
echo 3 > /proc/sys/vm/drop_caches