C 为什么第一个printf需要更长的时间?

C 为什么第一个printf需要更长的时间?,c,printf,rdtsc,C,Printf,Rdtsc,我当时正在玩高精度计时器,我的第一个测试之一是使用rdtsc测量printf。下面是我的测试prpgram及其输出。我注意到,第一次运行printf时,第一次打印所花费的时间是后续打印的25倍。为什么呢 #include <stdio.h> #include <stdint.h> // Sample code grabbed from wikipedia __inline__ uint64_t rdtsc(void) { uint32_t lo, hi;

我当时正在玩高精度计时器,我的第一个测试之一是使用rdtsc测量printf。下面是我的测试prpgram及其输出。我注意到,第一次运行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, world\n");
        counter[i] = rdtsc() - counter[i];
    }

    for (i = 0; i < 10; i++)
    {
        printf("counter[%d] = %lld\n", i, counter[i]);
        sum += counter[i];
    }
    printf("avg = %lld\n", sum/10);
    return 0;
}

(供参考,这是用OSX上的gcc编译的)

我的猜测是,在第一次调用printf时,stdout资源不在缓存中,调用将需要将其带到缓存中-因此速度较慢。 对于所有后续调用,缓存已处于热状态

第二种可能的解释是,如果这是在Linux上(也可能适用于OSX,我不确定),程序需要设置流方向。(ASCII与UNICODE)这是在第一次调用使用该流的函数时完成的,并且在流关闭之前是静态的。 我不知道设置此方向的开销是多少,但这是一次性成本


如果有人认为我完全错了,请随时纠正我。

也许是第一次,printf的代码不在指令缓存中,因此必须从主存加载。在随后的运行中,它已经在缓存中。

大约50微秒。可能是缓存问题?太短,与从硬盘加载无关,但从RAM加载一大块C I/O库是可信的。

它可能是某种类型的。

在硬件和软件设计方面,有一个压倒一切的原则表明,做了一百万次的事情的执行速度远比做了一次的事情的执行速度重要。由此推论,如果某件事做了一百万次,那么第一次做某件事所需的时间远没有其他99999件所需的时间重要。计算机今天比25年前快得多的最大原因之一是,设计师们一直致力于加快重复操作的速度,即使这样做可能会降低一次性操作的性能

作为一个简单的例子,从硬件的角度考虑内存设计的两种方法:(1)有一个内存存储,每个操作需要六十纳秒来完成;(2) 有几个级别的缓存;获取保存在第一级缓存中的字需要一纳秒;一个单词如果不在那里,但是在第二个级别上,就需要五个单词;一个不存在但在第三级的单词需要十个,一个不存在的单词需要六十个。如果所有的内存访问都是完全随机的,那么第一种设计不仅比第二种简单,而且性能也会更好。大多数内存访问都会导致CPU在从主内存取出数据之前在缓存中查找数据时浪费10纳秒。另一方面,如果80%的内存访问由第一个缓存级别满足,16%由第二个缓存级别满足,3%由第三个缓存级别满足,那么只有百分之一的内存需要访问主内存,那么这些内存访问的平均时间将为2.5ns。平均而言,这是简单内存系统速度的40倍


即使从磁盘预加载了整个程序,第一次运行“printf”之类的例程时,它和它需要的任何数据都不可能在任何级别的缓存中。因此,第一次运行时需要缓慢的内存访问。另一方面,一旦代码和它所需的大部分数据都被缓存,以后的执行就会快得多。如果一段代码在最快的缓存中重复执行,那么速度差很容易达到一个数量级。在许多情况下,针对快速情况进行优化会导致代码的一次性执行速度比其他情况慢得多(甚至比上面的示例所建议的更慢),但由于许多处理器花费大量时间运行少量代码,因此需要数百万或数十亿时间,在这些情况下获得的加速比只运行一次的例程的执行速度慢得多。

是的,OSX会延迟绑定符号
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