#c #caching #x86 #papi
#c #кэширование #x86 #papi
Вопрос:
Я использую библиотеку PAPI для считывания аппаратных счетчиков. Я заметил, что порядок вызова инициализации PAPI_library_init (PAPI_VER_CURRENT) влияет на результаты, которые я получаю. Моя инициализация и чтение массива выглядят следующим образом:
int retval;
/*
retval = PAPI_library_init(PAPI_VER_CURRENT);
if (retval != PAPI_VER_CURRENT) {
fprintf(stderr, "PAPI library init error!n");
exit(1);
}
*/
for(int i=0; i < arr_size; i ){
array[i].value = 1;
//_mm_clflush(amp;array[i]); flushing does not make difference.
}
_mm_mfence();
for(int i=0; i < arr_size; i ){
temp = array[i].value ;
}
_mm_mfence();
retval = PAPI_library_init(PAPI_VER_CURRENT);
if (retval != PAPI_VER_CURRENT) {
fprintf(stderr, "PAPI library init error!n");
exit(1);
}
Я полагаю, что необходимость второго цикла для чтения массива связана с протоколом coherence, но здесь это не должно иметь большого значения. После этого я добавляю собственные события MEM_LOAD_RETIRED в набор событий, который я хочу прочитать, и я использую PAPI_read в этом третьем цикле (я читаю его до и после цикла и в конце печатаю разницу) :
for(int i=0; i < arr_size; i ){
temp = array[i].value ;
}
где arr_size равен 1000, а размер каждого элемента массива равен 64 байтам (равен строке кэша). Я отключил все средства предварительной выборки . Я компилирую с флагом gcc -O3 для оптимизации и библиотекой -lpapi. с помощью этого кода для третьего цикла я получаю:
L1_HIT: 64, L1_MISS: 1011, L2_HIT: 15, L2_MISS: 996.
Однако, если я раскомментирую PAPI_library_init перед инициализацией массива и прокомментирую его после, я получу следующие результаты:
L1_HIT: 73, L1_MISS: 1004, L2_HIT: 990, L2_MISS: 14.
Я тестирую это на сервере skylake, размеры кэша составляют:
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 22528K
Теперь я немного смущен, почему инициализация papi влияет на эти результаты. это L2 попадает и пропускает это изменение. Все, что мне нужно, это третий цикл, и влияние первых двух циклов на счетчики, я полагаю, не учитывается.
Итак, любой намек на это был бы полезен, поскольку во всей документации говорится следующее: «PAPI_library_init() инициализирует библиотеку PAPI. Это должно быть вызвано перед использованием любых низкоуровневых функций PAPI. Если ваше приложение использует потоки, PAPI_thread_init (3) также должен быть вызван перед выполнением любых вызовов библиотеки, отличной от PAPI_library_init().»
Комментарии:
1. Вы можете проверить без
_mm_clflush(amp;array[i]);
? Можете ли вы проверить меньшие размеры массива, такие как 500 и 300 элементов вместо 1000? Вы уточнили объявление массива с помощьюvolatile
, чтобы компилятор не оптимизировал нагрузку на-O3
?2. @HadiBrais Да, у меня энергозависимый массив, поэтому чтение во временном режиме не будет оптимизировано. Я проверю без промывки. теперь я уверен, что получу тот же результат, но я постараюсь
3. @HadiBrais да, у меня такое же поведение без clflush. Просто чтобы убедиться, в этом первом цикле, когда я инициализирую array, он записывает первый раз в элемент, а затем удаляет эту строку кэша, верно? Я не подозревал об этом раньше, но я протестировал это сегодня, и, к моему удивлению, это наблюдение, которое я получил. Я читал об обнулении по требованию в другом посте, который, как я понял, был причиной случая RFO, верно? Так это тоже как-то связано с удалением строки кэша?
4. для 500 инициализация PAPI после дает результат: L1_HIT: 62, L1_MISS: 513, L2_HIT: 16, L2_MISS: 497, а инициализация до: L1_HIT: 67, L1_MISS: 510, L2_HIT: 377, L2_MISS: 133. похоже, поведение такое же. То же самое и для 300. После инициализации: L1_HIT: 83, L1_MISS: 304, L2_HIT: 6, L2_MISS: 298. инициализация перед массивом: L1_HIT: 82, L1_MISS: 302, L2_HIT: 117, L2_MISS: 185,
5. Это как если бы PAPI_library_init вызывал удаление всех строк L2. Глядя на исходный код, я не понимаю, почему это могло произойти. Как насчет MEM_LOAD_RETIRED. L3_MISS и MEM_LOAD_RETIRED. L3_HIT?