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

时间:2011-09-02 14:28:54

标签: c printf rdtsc

我正在使用高精度计时器,我的第一个测试之一是使用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, 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;
}

输出:

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编译的)

5 个答案:

答案 0 :(得分:5)

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

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

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

答案 1 :(得分:5)

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

答案 2 :(得分:4)

这大约是50微秒。也许是一个缓存问题?与从硬盘驱动器加载无关,但是可以从RAM中加载大量的C I / O库。

答案 3 :(得分:4)

它可以是某种lazy initialization

答案 4 :(得分:1)

在硬件和软件设计中,有一个最重要的原则,即一百万次执行的执行速度远比执行一次的执行速度重要得多。这样做的一个必然结果是,如果某些事情做了一百万次,那么第一次做某事所需的时间远不如其他999,999所需的时间重要。计算机如今比25年前快得多的最大原因之一是,设计人员专注于更快地重复操作,即使这样做可能会降低一次性操作的性能。

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

即使整个程序是从磁盘预加载的,第一次运行像“printf”这样的例程时,它或它所需的任何数据都不可能处于任何级别的缓存中。因此,第一次运行时将需要慢速内存访问。另一方面,一旦代码及其大部分所需数据被缓存,未来的执行将会快得多。如果在仍然处于最快缓存中的情况下重复执行一段代码,则速度差异可以很容易地达到一个数量级。对于快速情况的优化将在许多情况下导致一次性执行代码比其他情况要慢得多(比上面的示例所建议的更大)但是因为许多处理器花费大量时间来运行小块代码数百万或数十亿的时间,在那些情况下获得的加速比远远超过只执行一次的例程执行的减速。