2011-09-02 5 views
3

Я играл с высокоточными таймерами, и одним из моих первых тестов было использование rdtsc для измерения printf. Ниже приведена моя тестовая программа, за которой следует ее выход. То, что я заметил, это то, что при первом запуске printf он последовательно занимает примерно в 25 раз больше первого отпечатка, чем при последующих отпечатках. Почему это?Почему первый printf занимает больше времени?

#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 

(Для справки, это был скомпилирован с GCC на OSX)

ответ

5

Я полагаю, что на первый вызов PRINTF, стандартный вывод ресурсов не являются в кеше, и вызов должен будет вставить его в кеш - следовательно, он медленнее. Для всех последующих вызовов кеш уже теплый.

Вторым возможным объяснением является то, что если это относится к Linux (может также применяться к OSX, я не уверен), программе необходимо установить ориентацию потока. (ASCII против UNICODE) Это делается при первом вызове функции, использующей этот поток, и является статическим до тех пор, пока поток не завершится. Я не знаю, что накладные расходы при настройке этой ориентации, но это разовая стоимость.

Пожалуйста, не стесняйтесь исправить меня, если кто-нибудь подумает, что я совершенно неправ.

5

Возможно, в первый раз код для printf отсутствует в кэше команд, поэтому его необходимо загрузить из основной памяти. При последующих запусках он уже находится в кеше.

4

Это около 50 микросекунд. Возможно, проблема кеширования? Слишком короткий, чтобы быть чем-то связанным с загрузкой с жесткого диска, но правдоподобно для загрузки большого фрагмента библиотеки C I/O из ОЗУ.

1

Как в аппаратном, так и в программном обеспечении, существует принцип переопределения, который предполагает, что скорость выполнения чего-то, что делается миллион раз, гораздо важнее, чем скорость выполнения чего-то, что сделано один раз. Следствием этого является то, что если что-то сделано миллион раз, время, необходимое для выполнения чего-то в первый раз, гораздо менее важно, чем время, необходимое для других 999,999. Одна из самых больших причин, по которой компьютеры сегодня намного быстрее, чем 25 лет назад, заключается в том, что дизайнеры сосредоточены на том, чтобы делать повторные операции быстрее, даже если это может замедлить работу одноразовых операций.

В качестве простого примера с точки зрения аппаратного обеспечения рассмотрим два подхода к дизайну памяти: (1) имеется один магазин памяти, и каждая операция занимает шестьдесят наносекунд; (2) существует несколько уровней кеша; извлечение слова, которое удерживается на первом уровне кеша, займет одну наносекунду; слова, которого нет, но удерживается на втором уровне, займет пять; слово, которого нет, но находится на третьем уровне, займет десять, а того, чего нет, потребуется шестьдесят. Если бы все обращения к памяти были абсолютно случайными, первая конструкция была бы не только более простой, чем второй, но и лучшей. Большинство обращений к памяти вызовет у CPU отходы десяти наносекунд, которые будут искать данные в кэше перед выходом и извлечением из основной памяти. С другой стороны, если 80% доступа к памяти удовлетворяются первым уровнем кеша, 16% - вторым, а 3% - третьим, поэтому только один из ста должен выйти в основную память, тогда среднее время для этих обращений к памяти будет 2,5 нс. Это в сорок раз быстрее, в среднем, как более простая система памяти.

Даже если вся программа предварительно загружена с диска, при первом запуске такой процедуры, как «printf», ни она, ни какие-либо данные, которые она требует, скорее всего, будут на любом уровне кеша. Следовательно, при первом запуске потребуется медленный доступ к памяти. С другой стороны, как только код и большая часть его требуемых данных будут кэшированы, будущие исполнения будут намного быстрее. Если повторное выполнение фрагмента кода происходит, когда оно все еще находится в самом быстром кэше, разница в скорости может быть легко на порядок. Оптимизация для быстрого случая во многих случаях приведет к тому, что однократное выполнение кода будет намного медленнее, чем в противном случае (в еще большей степени, чем это было предложено выше), но поскольку многие процессоры тратят большую часть своего времени на небольшие куски кода в миллионы или миллиарды раз, ускорения, полученные в этих ситуациях, намного перевешивают любое замедление при выполнении подпрограмм, которые выполняются только один раз.

Смежные вопросы