Различное количество промахов кеша для одной и той же программы между несколькими запусками

Я использую Cachegrind для получения количества промахов кеша статической программы, скомпилированной без libc (просто _start, которая вызывает мою основную функцию, и системный вызов выхода в asm). Программа полностью детерминирована, инструкции и ссылки на память не меняются от одного запуска к другому. Кэш является полностью ассоциативным с LRU в качестве политики замены.

Однако я заметил, что количество промахов иногда меняется. В частности, количество промахов всегда одинаково, пока я не перейду в другой каталог:

 % cache=8 && valgrind --tool=cachegrind --I1=$((cache * 64)),$cache,64 --D1=$((cache * 64)),$cache,64 --L2=262144,4096,64 ./adpcm        
 ...
 ==31352== I   refs:      216,145,010
 ...
 ==31352== D   refs:      130,481,003  (95,186,001 rd   + 35,295,002 wr)
 ==31352== D1  misses:        240,004  (   150,000 rd   +     90,004 wr)
 ==31352== LLd misses:             31  (        11 rd   +         20 wr)

И если я буду выполнять одну и ту же команду снова и снова, я буду получать одни и те же результаты. Но если я запущу эту программу из другого каталога:

 % cd ..
 % cache=8 && valgrind --tool=cachegrind --I1=$((cache * 64)),$cache,64 --D1=$((cache * 64)),$cache,64 --L2=262144,4096,64 ./malardalen2/adpcm
 ...
 ==31531== I   refs:      216,145,010
 ...
 ==31531== D   refs:      130,481,003  (95,186,001 rd   + 35,295,002 wr)
 ==31531== D1  misses:        250,004  (   160,000 rd   +     90,004 wr)
 ==31531== LLd misses:             31  (        11 rd   +         20 wr)

И у меня даже другой результат из другого каталога.

Я также провел несколько экспериментов с инструментом Pin, и с этим мне не нужно менять каталог, чтобы получить другие значения. Но кажется, что набор возможных значений очень ограничен и точно такой же, как у Cachegrind.

Мой вопрос: каковы могут быть источники таких различий?

Моя первая подсказка заключается в том, что моя программа не выровнена в памяти таким же образом, и, как следствие, некоторые переменные, хранившиеся в той же строке в предыдущем запуске, больше не выровнены. Это также может объяснить ограниченное количество комбинаций. Но я думал, что этот cachegrind (и Pin) использовал виртуальные адреса, и я предполагаю, что ОС (Linux) всегда дает одни и те же виртуальные адреса. Любая другая идея?

Редактировать: Как вы можете догадаться, чтение LLd промахивается, программа использует только 31 различную строку кэша. Кроме того, кеш может содержать только 8 строк кеша. Таким образом, даже в реальности разницу нельзя объяснить тем, что кеш уже заполняется во второй раз (максимум, в L1 может оставаться только 8 строк).

Редактировать 2: отчет Cachegrind основан не на фактических промахах кэша (данных счетчиками производительности), а является результатом моделирования. По сути, он имитирует поведение кеша, чтобы подсчитать количество промахов. Поскольку последствия носят временный характер, это совершенно нормально и позволяет изменять свойства кеша (размер, ассоциативность).

Изменить 3: Я использую аппаратное обеспечение Intel Core i7 на Linux 3.2 x86_64. Флаги компиляции -static, а для некоторых программ -nostdlib (IIRC, меня сейчас нет дома).


person Maxime Chéramy    schedule 28.06.2013    source источник
comment
Кэш был заполнен другими данными во время второго запуска?   -  person Some programmer dude    schedule 28.06.2013
comment
@JoachimPileborg Я могу вернуться в предыдущий каталог, и у меня будет еще один счетчик промахов или, может быть, такой же, как раньше. Я думаю, что cachegrind запускается с пустым стеком для имитации кеша (а 31 — это количество холодных промахов).   -  person Maxime Chéramy    schedule 28.06.2013
comment
Когда вы меняете текущий рабочий каталог, вы также меняете соответствующую переменную среды (и ее длину). Поскольку копия всех переменных среды обычно хранится непосредственно над стеком, вы получаете другое распределение для переменных стека и разное количество промахов кэша. (И оболочка может изменить некоторые другие переменные, кроме PWD).   -  person Evgeny Kluev    schedule 02.07.2013
comment
@EvgenyKluev Верно, вчера тоже пытался выровнять стек с помощью переменной в начале программы и __attribute__ (aligned (64)). Однако это не решило мою проблему.   -  person Maxime Chéramy    schedule 02.07.2013
comment
Я думаю, что такое выравнивание стека может помочь только в том случае, если у вас полностью ассоциативный кеш. В случае только одностороннего, двухстороннего или четырехстороннего кеша вы все равно можете получить псевдонимы переменных стека и статических переменных по-разному в зависимости от размера среды. Вы можете попытаться выровнять его по размеру вашего кеша, а не только по 64 байтам...   -  person Evgeny Kluev    schedule 02.07.2013
comment
Пока я рассматриваю только полностью ассоциативные кэши. Почему статические переменные могут зависеть от размера среды?   -  person Maxime Chéramy    schedule 02.07.2013
comment
Нет, размещение статических переменных, скорее всего, не зависит от размера окружения, а размещение переменных стека зависит. И их можно перемещать относительно статических переменных. Но все это не имеет значения, так как ваш кеш полностью ассоциативен.   -  person Evgeny Kluev    schedule 02.07.2013
comment
Вы знаете, что кэш является общим для всей системы? Откуда вы знаете, что это не какая-то задача ядра, такая как kjournald, kswapd и т. д., которая записывает atime для некоторого каталога, в котором вы находитесь. Что-либо в системе может в конечном итоге вызвать попадание/промах кеша путем исключения некоторой случайной строки. Возможно, текущий каталог попадает в запись хеш-таблицы, которая использует одну и ту же строку.   -  person artless noise    schedule 05.07.2013
comment
Спасибо, но я изучаю тайники, ты думаешь, я этого не знаю? :). Cachegrind (как я его использую) не использует счетчики производительности, он только ловит адреса памяти (я думаю, виртуальные) и имитирует поведение кешей с массивами, чтобы получить количество промахов. Таким же образом вы можете изменить размер кеша.   -  person Maxime Chéramy    schedule 05.07.2013
comment
На каком оборудовании вы это запускаете? также каковы ваши флаги компиляции?...   -  person TheCodeArtist    schedule 07.07.2013
comment
Выполнение анализа горячих точек с помощью cachegrind/callgrind даст вы другой точки зрения. Может быть, программа следует другому пути кода в каждом из нескольких запусков?   -  person TheCodeArtist    schedule 07.07.2013
comment
@TheCodeArtist смотрите мои последние правки для ваших первых вопросов.   -  person Maxime Chéramy    schedule 07.07.2013
comment
@TheCodeArtist Как уже говорилось в вопросе, программы полностью детерминированы. Я пока отказался от использования Cachegrind, решил использовать трассировку памяти напрямую и делать то, что cachegrind делает сам. Трассировка предоставляется Gem5 и не меняется между двумя запусками, если я останавливаю рандомизацию адреса стека.   -  person Maxime Chéramy    schedule 07.07.2013
comment
@Maxime Поскольку вы озадачены различными промахами кеша в каждом случае, определение того, где именно они происходят в вашем коде, поможет вам понять поведение. Это можно быстро сделать с помощью callgrind с аннотированным исходным кодом, как описано в этой ссылке. . После того, как будут идентифицированы несколько строк кода, которые генерируют дополнительные промахи в кеше (тратят значительно разное количество времени). Изучение сборки должно привести нас к пониманию поведения. Я хотел бы попробовать это, а также. Можете ли вы поделиться src/ссылкой на двоичный файл adpcm?   -  person TheCodeArtist    schedule 07.07.2013
comment
ADPCM находится здесь: mrtc.mdh.se/projects/wcet/ wcet_bench/adpcm/adpcm.c Я изменил main, чтобы вызвать sys_exit (на ассемблере). Но наблюдаю, что практически с любой программой (с небольшим кешем)...   -  person Maxime Chéramy    schedule 07.07.2013


Ответы (2)


В Linux реализован метод «рандомизации макета адресного пространства» (http://en.wikipedia.org/wiki/Address_space_layout_randomization) по вопросам безопасности. И вы можете отключить это поведение следующим образом:

echo -n "0" > /proc/sys/kernel/randomize_va_space

Вы можете проверить это на этом примере:

#include <stdio.h>

int main() {
   char a;
   printf("%u\n", &a);
   return 0;
}

У вас всегда должно быть напечатано одно и то же значение.

До:

 % ./a.out
4006500239
 % ./a.out
819175583
 % ./a.out
2443759599
 % ./a.out
2432498159

После:

 % ./a.out
4294960207
 % ./a.out
4294960207
 % ./a.out
4294960207
 % ./a.out
4294960207

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

Редактировать: Это, по-видимому, не решает проблему полностью, но я думаю, что это было одной из причин. Я дам награду любому, кто может помочь мне решить эту проблему.

person Maxime Chéramy    schedule 01.07.2013
comment
Если я запускаю этот пример в cachegrind, у меня будут другие значения (например, 4278190991), и это значение изменится... И когда у меня будет другое значение, у меня также будет другое количество промахов кеша. - person Maxime Chéramy; 01.07.2013
comment
Может ли ядро ​​рандомизировать, где загружаются бинарные и/или разделяемые библиотеки? Вариант Linux, который я здесь использую, не работает, но я знаю, что некоторые из них работают. - person Art; 04.07.2013
comment
Программа скомпилирована в статике (поэтому нет общей библиотеки для загрузки). Адреса глобальных переменных не меняются. - person Maxime Chéramy; 04.07.2013
comment
Сделайте паузу программы после запуска. Проверьте его структуру памяти с помощью команды pmap. Что меняется при каждом выполнении? - person Art; 04.07.2013

Кажется, это известное поведение в valgrind:

Я использовал пример, который выводит базовый адрес кеша, также я отключил рандомизацию макета.

Я дважды запускал исполняемый файл, получая одинаковые результаты в обоих запусках:

D   refs:       40,649  (28,565 rd   + 12,084 wr)
==15016== D1  misses:     11,465  ( 8,412 rd   +  3,053 wr)
==15016== LLd misses:      1,516  ( 1,052 rd   +    464 wr)
==15016== D1  miss rate:    28.2% (  29.4%     +   25.2%  )
==15016== LLd miss rate:     3.7% (   3.6%     +    3.8%  )

villar@localhost ~ $ cache=8 && valgrind --tool=cachegrind --I1=$((cache * 64)),$cache,64 --D1=$((cache * 64)),$cache,64 --L2=262144,4096,64 ./a.out 

==15019== D   refs:       40,649  (28,565 rd   + 12,084 wr)
==15019== D1  misses:     11,465  ( 8,412 rd   +  3,053 wr)
==15019== LLd misses:      1,516  ( 1,052 rd   +    464 wr)
==15019== D1  miss rate:    28.2% (  29.4%     +   25.2%  )
==15019== LLd miss rate:     3.7% (   3.6%     +    3.8%  )

Согласно документации cachegrind (http://www.cs.washington.edu/education/courses/cse326/05wi/valgrind-doc/cg_main.html)

Еще одна вещь ничего не стоит, это то, что результаты очень чувствительны. Изменение размера файла >valgrind.so, размера профилируемой программы, или даже длины ее имени может исказить результаты. Вариации будут небольшими, но не ожидайте идеально повторяющихся результатов, если ваша программа вообще изменится. Хотя эти факторы означают, что вы не должны доверять результатам, чтобы быть сверхточными, мы надеемся, что они должны быть достаточно близкими, чтобы быть полезными.

Прочитав это, я изменил имя файла и получил следующее:

villar@localhost ~ $ mv a.out a.out2345345345
villar@localhost ~ $ cache=8 && valgrind --tool=cachegrind --I1=$((cache * 64)),$cache,64 --D1=$((cache * 64)),$cache,64 --L2=262144,4096,64 ./a.out2345345345 

==15022== D   refs:       40,652  (28,567 rd   + 12,085 wr)
==15022== D1  misses:     10,737  ( 8,201 rd   +  2,536 wr)
==15022== LLd misses:      1,517  ( 1,054 rd   +    463 wr)
==15022== D1  miss rate:    26.4% (  28.7%     +   20.9%  )
==15022== LLd miss rate:     3.7% (   3.6%     +    3.8%  )

Изменение имени обратно на «a.out» дало мне точно такой же результат, как и раньше.

Обратите внимание, что изменение имени файла или пути к нему изменит базу стека!! и это может быть причиной после прочтения того, что сказал г-н Евгений в предыдущем комментарии

Когда вы меняете текущий рабочий каталог, вы также меняете соответствующую переменную среды (и ее длину). Поскольку копия всех переменных среды обычно хранится непосредственно над стеком, вы получаете другое распределение для переменных стека и разное количество промахов кэша. (И оболочка может изменить некоторые другие переменные, кроме "PWD").

РЕДАКТИРОВАТЬ: В документации также говорится:

Запуск/закрытие программы вызывает множество функций, которые не интересны и только усложняют вывод. Было бы неплохо их как-то исключить.

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

person Emilcasvi    schedule 08.07.2013
comment
+1 за цитату из документации и попытку воспроизвести. Однако однажды у меня не было смены адреса стека и разных результатов. Но у меня не получилось воспроизвести... - person Maxime Chéramy; 08.07.2013
comment
Вы можете проверить каждый промах по строке кода, которая его производит, с помощью cg_anotate (cs.swan.ac.uk/~csoliver/ok-sat-library/internet_html/doc/doc/) Другой вариант: valgrind имитирует кеш, преобразовывая инструкции x86 в микрооперации. Поскольку ваш код достаточно мал и не имеет никакой зависимости, вы можете модифицировать valgrind, чтобы отслеживать и анализировать блоки кода и адреса, на которые ссылаются. Дополнительную информацию о том, как это сделать, можно найти на странице cs.washington.edu/education/courses/cse326/05wi/valgrind-doc/. - person Emilcasvi; 08.07.2013