Фиксированные счетчики не учитываются постоянно, только если программное обеспечение их разрешило. Обычно (сторона ядра) perf
делает это вместе со сбросом их в ноль перед запуском программы.
Фиксированные счетчики (например, программируемые счетчики) имеют биты, которые управляют
они учитываются в user, kernel или user + kernel (т.е. всегда). Я предполагаю, что код ядра perf
в Linux оставляет их равными нулю, когда ничего их не использует.
Если вы хотите использовать raw RDPMC самостоятельно, вам нужно либо запрограммировать / включить счетчики (путем установки соответствующих битов в IA32_PERF_GLOBAL_CTRL
и IA32_FIXED_CTR_CTRL
MSR), либо заставить perf сделать это для вас, продолжая работать ваша программа под perf
. например perf stat ./a.out
Если вы используете perf stat -e instructions:u ./perf ; echo $?
, фиксированный счетчик будет фактически обнуляться перед вводом кода, поэтому вы получите непротиворечивые результаты от использования rdpmc
один раз. В противном случае, например по умолчанию -e instructions
(не: u) вы не знаете начальное значение счетчика. Это можно исправить, взяв дельту, прочитав счетчик один раз при запуске, а затем один раз после цикла.
Состояние выхода - всего 8 бит в ширину, поэтому этот небольшой взлом, чтобы избежать printf или write()
, работает только для очень маленьких отсчетов.
Это также означает, что бессмысленно создавать полный 64-битный результат rdpmc
: старшие 32 бита входов не влияют на младшие 8 бит результата sub
, потому что перенос распространяется только от низкого до высокого. В общем, если вы не рассчитываете на счет> 2 ^ 32, просто используйте результат EAX. Даже если необработанный 64-битный счетчик обернут в течение измеренного вами интервала, результат вычитания все равно будет правильным маленьким целым числом в 32-битном регистре.
Упрощено даже больше, чем в вашем вопросе. Также обратите внимание на отступы операндов, чтобы они могли оставаться в последовательном столбце даже для мнемоник длиннее 3 букв.
segment .text
global _start
_start:
mov ecx, 1<<30 ; fixed counter: instructions
rdpmc
mov edi, eax ; start
mov edx, 10
.loop:
dec edx
jnz .loop
rdpmc ; ecx = same counter as before
sub eax, edi ; end - start
mov edi, eax
mov eax, 231
syscall ; sys_exit_group(rdpmc). sys_exit isn't wrong, but glibc uses exit_group.
Запуская это в perf stat ./a.out
или perf stat -e instructions:u ./a.out
, мы всегда получаем 23
из echo $?
(instructions:u
показывает 30, что на 1 больше фактического количества инструкций, которые выполняет эта программа, включая syscall
)
23 инструкции - это точно количество инструкций строго после первой rdpmc
, но включая 2 rdpmc
.
Если мы закомментируем первый rdpmc
и запустим его под perf stat -e instructions:u
, мы последовательно получим 26
в качестве состояния выхода и 29
с perf
. rdpmc
является 24-й инструкцией, которую нужно выполнить. (И RAX вначале инициализируется нулем, потому что это статический исполняемый файл Linux, поэтому динамический компоновщик не работал до _start
). Интересно, считается ли sysret
в ядре как "пользовательская" инструкция.
Но с первым rdpmc
, закомментированным, выполнение под perf stat -e instructions
(не: u) дает произвольные значения, так как начальное значение счетчика не фиксировано. Итак, мы просто принимаем (некоторая произвольная начальная точка + 26) мод 256 в качестве состояния выхода.
Но обратите внимание, что RDPMC не инструкция сериализации и может выполняться не по порядку. В общем случае вам может понадобиться lfence
или (как предлагает Джон МакКалпин в связанной с вами теме) предоставление ECX ложной зависимости от результатов инструкций, которые вас интересуют. например and ecx, 0
/ or ecx, 1<<30
работает, потому что в отличие от обнуления xor, and ecx,0
не является нарушением зависимости.
Ничего странного в этой программе не происходит, потому что интерфейс является единственным узким местом, поэтому все инструкции выполняются в основном сразу после их выдачи. Кроме того, rdpmc
находится сразу после цикла, поэтому, вероятно, неверный прогноз ветвления ветви выхода из цикла препятствует его выдаче в серверную часть OoO до завершения цикла.
PS для будущих читателей: один из способов включить RDPMC в пользовательском пространстве без каких-либо пользовательских модулей, кроме тех, которые требуются perf
, документирован в perf_event_open(2)
:
echo 2 | sudo tee /sys/devices/cpu/rdpmc # enable RDPMC always, not just when a perf event is open