我正在使用高精度计时器,我的第一个测试是使用rdtsc来测量printf。下面是我的测试程序及其输出。我注意到的是,第一次运行printf时,第一次打印的时间总是比后续打印的时间长25倍。为什么呢?
#include <stdio.h>
#include <stdint.h>
// Sample code grabbed from wikipedia
__inline__ uint64_t rdtsc(void)
{
uint32_t lo, hi;
__asm__ __volatile__ (
"xorl %%eax,%%eax n cpuid"
::: "%rax", "%rbx", "%rcx", "%rdx");
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return (uint64_t)hi << 32 | lo;
}
int main(int argc, const char *argv[])
{
unsigned int i;
uint64_t counter[10];
uint64_t sum = 0;
for (i = 0; i < 10; i++)
{
counter[i] = rdtsc();
printf("Hello, worldn");
counter[i] = rdtsc() - counter[i];
}
for (i = 0; i < 10; i++)
{
printf("counter[%d] = %lldn", i, counter[i]);
sum += counter[i];
}
printf("avg = %lldn", sum/10);
return 0;
}
输出:
Hello, world
Hello, world
Hello, world
Hello, world
Hello, world
Hello, world
Hello, world
Hello, world
Hello, world
Hello, world
counter[0] = 108165
counter[1] = 6375
counter[2] = 4388
counter[3] = 4388
counter[4] = 4380
counter[5] = 4545
counter[6] = 4215
counter[7] = 4290
counter[8] = 4237
counter[9] = 4320
avg = 14930
(作为参考,这是用OSX上的gcc编译的)
我的猜测是,在第一次调用printf时,标准输出资源不在缓存中,调用将需要将其放入缓存中-因此速度较慢。对于所有后续调用,缓存已经是热的。
第二个可能的解释是,如果这是在Linux上(可能也适用于OSX,我不确定),程序需要设置流方向。(ASCII vs. UNICODE)这是在使用该流的函数的第一次调用时完成的,并且在流关闭之前是静态的。我不知道设置这个朝向的开销是多少,但是它是一次性的开销。
如果有人认为我完全错了,请随时纠正我。
也许第一次,printf
的代码不在指令缓存中,所以它必须从主存加载。在随后的运行中,它已经在缓存中了。
这大约是50微秒。也许是缓存问题?太短,无法从硬盘加载,但对于从RAM加载大块的C I/O库是可信的。
可以是某种延迟初始化
在硬件和软件设计中,有一个最重要的原则,那就是做了一百万次的事情的执行速度比只做一次的事情的执行速度重要得多。由此推论,如果某件事做了一百万次,那么第一次做这件事所需的时间远不如其他999,999次所需要的时间重要。今天的计算机比25年前快得多的最大原因之一是设计师们专注于使重复操作更快,即使这样做可能会降低一次性操作的性能。
作为硬件角度的简单示例,考虑两种内存设计方法:(1)有一个单一的内存存储,每个操作需要60纳秒才能完成;(2)存在多级缓存;读取保存在第一层缓存中的字将花费1纳秒;一个不存在但被保存在第二层的单词需要5分钟;一个不存在但在第三层的单词需要10分,一个不存在的单词需要60分。如果所有的内存访问都是完全随机的,那么第一种设计不仅会比第二种设计简单,而且性能也会更好。大多数内存访问将导致CPU在从主内存取出数据之前,在缓存中查找数据浪费10纳秒。另一方面,如果80%的内存访问是由第一个缓存级别满足的,16%是由第二个缓存级别满足的,3%是由第三个缓存级别满足的,那么只有百分之一的内存需要访问主存,那么这些内存访问的平均时间将是2.5ns。这比简单的记忆系统平均快40倍。
即使整个程序都是从磁盘预加载的,第一次运行像"printf"这样的例程时,它和它需要的任何数据都不可能在任何级别的缓存中。因此,第一次运行时将需要慢速内存访问。另一方面,一旦代码及其所需的大部分数据被缓存,以后的执行将会快得多。如果重复执行一段代码,而它仍然在最快的缓存中,速度差异很容易达到一个数量级。在许多情况下,针对快速情况进行优化将导致代码的一次性执行比其他情况要慢得多(甚至比上面的例子所建议的程度更大),但由于许多处理器花费大量时间运行数百万或数十亿次的小代码片段,因此在这些情况下获得的加速远远超过仅运行一次的例程执行的任何减速。