Как использовать инструкцию rdpmc для подсчета промахов кеша L1d?

Мне интересно, есть ли какое-нибудь отдельное событие, которое может фиксировать промахи кеша L1D. Я попытался зафиксировать промах кэша L1d, измерив задержку для доступа к определенной памяти с помощью rdtsc в начале. В моих настройках, если происходит промах кеша L1d, он должен попасть в кеш L2. Поэтому я измеряю задержку доступа к памяти с помощью RDTSC и сравниваю ее с задержкой кэша L1 и задержкой кэша L2. Однако из-за шума я не могу понять, попадает ли он в L1 или L2. Поэтому я решил использовать RDPMC.

Я обнаружил, что несколько API-интерфейсов предоставляют некоторые функции для простого отслеживания событий perf, но я хотел бы использовать инструкцию RDPMC непосредственно в своей тестовой программе. Я обнаружил, что MEM_INST_RETIRED.ALL_LOADS-MEM_LOAD_RETIRED.L1_HIT можно использовать для подсчета количества удаленных инструкций загрузки, которые отсутствуют в L1D. (подсчет промахов кэша L1 с помощью PAPI_read_counters дает неожиданные результаты). Однако похоже, что в этом посте говорится о papi Api.

Как я могу найти, какие значения должны быть присвоены регистру ecx перед выполнением инструкции rdpmc для захвата определенных событий? Кроме того, мне интересно, есть ли какое-либо отдельное событие, которое может сказать мне, что промах L1 происходит для одной инструкции загрузки памяти между двумя инструкциями rdpmc, расположенными рядом друг с другом, как показано ниже.

c = XXX; //I don't know what value should be assigned for what perf counter..
asm volatile(
    "lfence"
    "rdpmc" 
    "lfence"
    "mov (0xdeadbeef), %%r10"//read memory
    "mov %%eax, %%r10        //read lower 32 bits of counter
    "lfence"                
    "rdpmc"                  //another rdpmc to capture difference
    "sub %%r10, %%eax        //sub two counter to get difference
    :"=a"(a)
    :"c"(c)
    :"r10", "edx");

введите описание изображения здесь

В настоящее время я использую кофемашину с кофейным озером 9900k, поэтому я поискал номер счетчика производительности для машины с кофе-озером в руководстве Intel. Кажется, что просто захвата двух MEM_LOAD_RETIRED.L1_HIT до и после инструкции загрузки достаточно, чтобы захватить событие, но я не уверен, можно ли это сделать .. Также я не знаю, как кодировать это событие perf как Регистр ecx.

Наконец, мне интересно, требует ли последовательная инструкция rdpmc каких-либо инструкций сериализации. В моем случае, поскольку я помещаю только инструкцию загрузки и измеряю, происходит ли промах кэша L1d или нет, я заключаю первую инструкцию rdpmc с инструкцией lfence и помещаю еще одну инструкцию lfence перед последним rdpmc, чтобы гарантировать, что инструкция загрузки завершится до второго rdpmc.

Добавлен код

asm volatile (                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
        "lfence\n\t"                                                                                                                                                                                                                                                                              
        "rdpmc\n\t"                                                                                                                                                                                                                                                                               
        "lfence\n\t"                                                                                                                                                                                                                                                                              
        "mov %%eax, %%esi\n\t"                                                                                                                                                                                                                                                                    
        //measure                                                                                                                                                                                                                                                                                 
        "mov (%4), %%r10\n\t"                                                                                                                                                                                                                                                                     
        "lfence\n\t"                                                                                                                                                                                                                                                                              
        "rdpmc\n\t"                                                                                                                                                                                                                                                                               
        "lfence\n\t"                                                                                                                                                                                                                                                                              
        "sub %%esi, %%eax\n\t"                                                                                                                                                                                                                                                                    
        "mov %%eax, (%0)\n\t"
        :
        :"r"(&perf[1]), "r"(&perf[2]), "r"(&perf[3]),                                                                                                                                                                                                                                              
         "r"(myAddr),   "c"(0x0)                                                                                                                                                                                                         
        :"eax","edx","esi","r10", "memory");

Также я закрепил свое ядро ​​номер 3 с помощью isolcpu и отключил гиперпоточность для тестирования. Регистр MSR был вычислен с помощью команды ниже

    sudo wrmsr -p 3 0x186 0x4108D1 #L1 MISS

person ruach    schedule 05.10.2020    source источник
comment
Вы забыли "\n" в конце каждой строки этого встроенного asm-оператора; конкатенация строк вставит весь этот текст вместе без пробелов.   -  person Peter Cordes    schedule 06.10.2020
comment
lfence около rdpmc, вероятно, понадобится; Я не думаю, что он ждет, пока предыдущая инструкция не исчезнет, ​​прежде чем читать счетчик. Кстати, современный GCC имеет не сломанный __rdpmc внутренний. (Более старый GCC забыл рассматривать его как volatile, так же как и CSE). Извините, я не знаю с помощью PAPI, как узнать, какой номер HW-счетчика ядро ​​выбрало для события.   -  person Peter Cordes    schedule 06.10.2020
comment
Будет проще использовать PAPI API для настройки счетчика и получения показаний до и после вашего тестового кода. И ваш тестовый код должен быть разработан так, чтобы многократно повторять тестируемую последовательность. По умолчанию rdpmc / rdmsr для perfcounters должен быть отключен для кода пользовательского пространства с помощью флага PCE в CR4 - felixcloutier.com / x86 / rdpmc (echo 2 > /sys/bus/event_source/devices/cpu/rdpmc); с включенным доступом только к ядру Linux. Существуют методы измерения задержки кэша без счетчиков производительности: 7-cpu.com/utils.html и lmbench / src / lat_mem_rd.c   -  person osgx    schedule 08.10.2020
comment
Обратите внимание, что ваш оператор asm не работает: вы сбиваете EAX, не сообщая об этом компилятору. Используйте выход EAX "=&a(perf[1]) Early-clobber и просто опустите это последнее mov сохранение в (%0). Позвольте компилятору обрабатывать перемещение данных за пределы временной области. (Выполнение подпрограммы внутри может упростить ограничения, но вы можете просто произвести запуск и остановку вывода.)   -  person Peter Cordes    schedule 09.10.2020
comment
@PeterCordes Спасибо, я пропустил регистрацию eax. Я изменил свой ассемблерный код. Причина, по которой я не использую = & a, заключалась в том, что я использую несколько назначений для разных вещей perf [x], поэтому я изменил свою сборку с = & a на multiple = r (для простоты я удалил дальнейшие инструкции rdpmc, чтобы измерить еще один промах в кэше L1 с помощью perf [2], перф. [3] ...)   -  person ruach    schedule 09.10.2020
comment
@PeterCordes Я не могу понять последнюю часть. что вы имеете в виду, делая саб внутри?   -  person ruach    schedule 09.10.2020
comment
Я имею в виду, что вы можете просто произвести запуск и остановку как 2 отдельных выхода регистра, а в C сделать end-start. Нет необходимости иметь инструкцию sub как часть шаблона asm.   -  person Peter Cordes    schedule 09.10.2020
comment
ага, вы имеете в виду, что просто присваиваете два разных результата rdpmc конечной и начальной переменной и подключаете их за пределами asm?   -  person ruach    schedule 09.10.2020


Ответы (1)


Вот пример использования rdpmc: https://github.com/jdmccalpin/low-overhead-timers Джона https://stackoverflow.com/a/60267195 (http://sites.utexas.edu/jdm4372/2018/07/23/comments-on-timing-short-code-sections-on-intel-processors/).

Также был упомянут готовый инструмент для измерения инструкций: https://arxiv.org/pdf/1911.03282.pdf https://github.com/andreas-abel/nanoBench

В этом ответе https://stackoverflow.com/a/60267531 есть пример использования perf_event_open для настройки счетчика событий и rdpmc для счетчика чтения.

rdpmc не сериализуется и также не монотонен между двумя несериализованными rdpmc в соответствии с https://www.felixcloutier.com/x86/rdpmc:

Инструкция RDPMC не является инструкцией сериализации; то есть, это не означает, что все события, вызванные предыдущими инструкциями, были завершены или что события, вызванные последующими инструкциями, не начались. Если требуется точное количество событий, программное обеспечение должно вставить инструкцию сериализации (такую ​​как инструкция CPUID) до и / или после инструкции RDPMC.

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

Для создания селекторов событий PMC можно использовать библиотеку jevents: https://github.com/andikleen/pmu-tools/tree/master/jevents. Он используется внутри последних версий инструмента профилирования perf linux. У jevents также есть простой API для использования команды rdpmc

if (rdpmc_open(PERF_COUNT_HW_CPU_CYCLES, &ctx) < 0) ... error ...
start = rdpmc_read(&ctx);
... your workload ...
end = rdpmc_read(&ctx);

showevtinfo из libpfm4 может генерировать идентификатор события, совместимый с форматом ecx rdpmc, но я не уверен: https://stackoverflow.com/a/46370111 < / а>

С помощью nanobench мы можем проверить исходный код на наличие событий Skylake: https://github.com/andreas-abel/nanoBench/blob/master/configs/cfg_Skylake_common.txt

D1.01 MEM_LOAD_RETIRED.L1_HIT
D1.08 MEM_LOAD_RETIRED.L1_MISS
D1.02 MEM_LOAD_RETIRED.L2_HIT
D1.10 MEM_LOAD_RETIRED.L2_MISS
D1.04 MEM_LOAD_RETIRED.L3_HIT
D1.20 MEM_LOAD_RETIRED.L3_MISS

анализируется в https://github.com/andreas-abel/nanoBench/blob/master/common/nanoBench.c parse_counter_configs() как pfc_configs[n_pfc_configs].evt_num точка pfc_configs[n_pfc_configs].umask; закодировано в configure_perf_ctrs_programmable как

        uint64_t perfevtselx = read_msr(MSR_IA32_PERFEVTSEL0+i);
        perfevtselx &= ~(((uint64_t)1 << 32) - 1);

        perfevtselx |= ((config.cmask & 0xFF) << 24);
        perfevtselx |= (config.inv << 23);
        perfevtselx |= (1ULL << 22);
        perfevtselx |= (config.any << 21);
        perfevtselx |= (config.edge << 18);
        perfevtselx |= (os << 17);
        perfevtselx |= (usr << 16);

        perfevtselx |= ((config.umask & 0xFF) << 8);
        perfevtselx |= (config.evt_num & 0xFF);

        write_msr(MSR_IA32_PERFEVTSEL0+i, perfevtselx);

Итак, два младших байта значения регистра, записанные в MSR IA32_PERF_EVTSELx, - это evt_num и umask. Не знаю, как это переведено в формат rdpmc ecx.

Джон говорит, что команда rdpmc занимает что-то в диапазоне 24-40 циклов, и описывает, что архитектура Intel делает невозможным изменение программирования выбора события счетчика производительности из пользовательского пространства с низкой задержкой / накладными расходами. https://community.intel.com/t5/Software-Tuning-Performance/Capturing-multiple-events-simhibitedly-using-RDPMC-instruction/td-p/1097868

И документация rdpmc говорит о том же https://www.felixcloutier.com/x86/rdpmc. :

Регистр ECX определяет тип счетчика (если процессор поддерживает архитектурный мониторинг производительности) и индекс счетчика. Счетчики производительности общего или специального назначения указаны с ECX [30] = 0.

ECX содержит не точное событие для подсчета, а индекс счетчика. Есть 2, 4 или 8 программируемых счетчиков производительности, и вы должны сначала использовать wrmsr (в режиме ядра), чтобы настроить какой-либо счетчик, например, с MSR IA32_PERF_EVTSEL0 для установки счетчика с индексом 0, а затем использовать rdpmc с ecx [30] = 0 и ecx [29: 0] = 0; с MSR IA32_PERF_EVTSEL3 используйте rdpmc с ecx [30] = 0 и ecx [29: 0] = 3.

Я думаю, что будет проще использовать PAPI API для настройки счетчика и получения показаний до и после вашего тестового кода. Но вызов API увеличивает накладные расходы, поэтому ваш тестовый код должен быть разработан так, чтобы повторять последовательность, которую нужно протестировать, несколько раз (тысячи и более). По умолчанию rdpmc / rdmsr для perfcounters отключены для кода пользовательского пространства с помощью флага PCE в CR4 - https://www.felixcloutier.com/x86/rdpmc (echo 2 > /sys/bus/event_source/devices/cpu/rdpmc); с включенным доступом только к ядру Linux. И wrmsr для настройки счетчика тоже отключен.

Существует несколько известных методов измерения задержки иерархии кеша без счетчиков производительности: https://www.7-cpu.com/utils.html и lmbench / src / lat_mem_rd.c, но для получения фактической задержки кеширования требуется некоторая ручная постобработка.

person osgx    schedule 08.10.2020
comment
Спасибо за очень подробные примеры и ответы. Для сериализации достаточно ли для этого сэндвич инструкции rdpmc с lfence ?? Я успешно настроил несколько регистров, необходимых для отслеживания промахов в кэше L1, записав регистры msr и настроив регистр ecx, как вы указали. Когда я выполняю свою инструкцию по загрузке памяти с двумя инструкциями rdpmc, отслеживающими промах кэша L1, например 1000 раз, в течение 960 раз, я не получал промах кэша L1, но примерно 40- ~ 60 раз я мог получить промах кэша L1 - person ruach; 08.10.2020
comment
Хотя моя текущая среда была полностью ограничена параметром ядра isolcpus и изолировала ядра, но получаю какой-то странный результат ... это должно быть 1000 L1, а не 960 .. - person ruach; 08.10.2020
comment
Процессор Intel имеет очень агрессивные устройства предварительной выборки аппаратного кеша (проверьте stackoverflow.com/questions/784041, почти невозможно выполнить 3 чтения в те же 4 килобайта без срабатывания предварительной выборки). Также проверьте свой код с помощью простого счетчика, например B1.01 UOPS_EXECUTED.THREAD, чтобы проверить, насколько искажен rdpmc. Требуется зазор между rdpmcs; lfence до и после вашего тестового кода может помочь. Не могли бы вы поделиться небольшим и полным примером вашего тестового кода? - person osgx; 08.10.2020
comment
не могли бы вы проверить мой обновленный ответ? Также я отключил все устройства предварительной загрузки в BIOS. Для обновленного кода перед выполнением сборки я предварительно загружаю запись в кеш с помощью операции чтения. так что он должен быть там, но большую часть времени он попадает, но иногда не попадает. не знаю почему .. - person ruach; 08.10.2020
comment
Я не могу понять, что вы измеряли, потому что нет полного примера вашего тестового кода. Я имею в виду полный исходный код, который можно скачать, скомпилировать и запустить. Что ты читал? Вмещается ли этот массив в кеш L1? Есть ли сглаживание между частями этого массива? - person osgx; 09.10.2020
comment
На самом деле это IA32_PERFEVTSEL0 в руководствах, без подчеркивания, адрес MSR 0x186 - person Lewis Kelsey; 20.03.2021