Почему второй вызов memset намного быстрее первого?

Я предполагаю, что это из-за какого-то кеша, но мне все еще интересно.

При использовании memset во второй раз в той же памяти код выполняется намного быстрее.

возьмем этот кусок кода:

> for_stackoverflow.c

#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>

int main() 
{
  long long size = 1 << 29;
  int i;
  char * mem;
  clock_t start, end;
  double how_long;  
  
  mem = malloc(size);

  start = clock(); {
    memset(mem, 0xde, size);
  } end = clock();

  how_long = ((double) (end - start)) / CLOCKS_PER_SEC;
  printf("%f\n", how_long);
  
  start = clock(); {
    memset(mem, 0xad, size);   
  } end = clock();

  how_long = ((double) (end - start)) / CLOCKS_PER_SEC;
  printf("%f\n", how_long);
  
  free(mem);

  return 0;
} 

и скомпилируйте его без какой-либо оптимизации (иначе memset будет пропущен, я думаю, это потому, что он не появится при выполнении objdump objdump -dw a.out)):

$ gcc -O0 for_stackoverflow.c

результат на моей машине (gnu / linux ubuntu 20.04 intel i5):

$ ./a.out 
0.244824
0.044119

... И второй memset работал быстрее, чем первый.

Я могу получить любопытный результат, например, если поставить половину размера, получится ровным:

    memset(mem, 0xad, size); -> memset(mem, 0xde, size/2);
    memset(mem, 0xad, size); -> memset(mem, 0xad, size);   

$./a.out
0.145827
0.141934

Это заставляет меня думать, что повторный запуск памяти намного быстрее.

Я не понимаю, какая оптимизация происходит в этом примере. Есть ли кто-нибудь, кто знает, что происходит?

Помощь будет очень признательна


person Antoine Huyghe    schedule 27.11.2020    source источник
comment
почему -00, а не -02 или -03?   -  person dreamcrash    schedule 27.11.2020
comment
страничные ошибки, кеш и TLB, а также разогрев частоты процессора. Кроме того, возможно, даже ленивые накладные расходы на динамическую компоновку при самом первом вызове! Но, как вы показали во втором тесте, ошибки страниц из-за ленивого выделения делают первое касание каждой страницы намного более дорогостоящим, так что это доминирует в стоимости.   -  person Peter Cordes    schedule 27.11.2020
comment
Это один из редких случаев, когда отключение оптимизации (-O0) не имеет особого значения; Подавляющее большинство работы происходит в написанной вручную библиотечной функции asm.   -  person Peter Cordes    schedule 27.11.2020
comment
@PeterCordes Использование -O3 может даже оказаться контрпродуктивным, поскольку компилятор может оптимизировать вызов memset(). Изменить: время сокращается до нескольких мкс для обеих частей с -O1.   -  person 12431234123412341234123    schedule 27.11.2020
comment
@ 12431234123412341234123 - Да, GCC понимает, что malloc возвращает память, на которую больше ничего не указывает, поэтому он может доказать, что хранилища мертвы, и оптимизировать их. gcc -O3 -fno-builtin-malloc может генерировать вызовы функций, которые вы хотите протестировать, с более эффективными вызовами между ними, если первый набор памяти все еще не обрабатывается как мертвые хранилища. (Вероятно, нет, потому что эта программа выполняет некоторые вызовы функций между двумя синхронизированными регионами.)   -  person Peter Cordes    schedule 27.11.2020