rdpmc: удивительное поведение - PullRequest
4 голосов
/ 17 мая 2019

Я пытаюсь понять инструкцию rdpmc.Таким образом, у меня есть следующий код asm:

segment .text
global _start

_start:
    xor eax, eax
    mov ebx, 10
.loop:
    dec ebx
    jnz .loop

    mov ecx, 1<<30
    ; calling rdpmc with ecx = (1<<30) gives number of retired instructions
    rdpmc
    ; but only if you do a bizarre incantation: (Why u do dis Intel?)
    shl rdx, 32
    or  rax, rdx

    mov rdi, rax ; return number of instructions retired.
    mov eax, 60
    syscall

(Реализация - перевод rdpmc_instructions () .) Я считаю, что этот код должен выполнить 2 * ebx + 3 инструкции передпри выполнении инструкции rdpmc, поэтому я ожидаю (в данном случае), что я получу возвращаемый статус 23.

Если я запускаю perf stat -e instruction:u ./a.out в этом двоичном файле, perf говорит мне, что явыполнил 30 инструкций, что выглядит примерно так.Но если я выполняю двоичный файл, я получаю статус возврата 58 или 0, не детерминированный.

Что я здесь сделал неправильно?

Ответы [ 2 ]

3 голосов
/ 18 мая 2019

Фиксированные счетчики не учитываются постоянно, только если программное обеспечение их разрешило. Обычно (сторона ядра) 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
2 голосов
/ 18 мая 2019

Первый шаг - убедиться, что счетчики производительности, которые вы хотите использовать, включены в регистр IA32_PERF_GLOBAL_CTRL MSR, расположение которого показано на рис. 18-8 в Intel Manual Volume 3 (January 2019).Это легко сделать, загрузив модуль ядра MSR (sudo modprobe msr) и выполнив следующую команду:

sudo rdmsr -a 0x38F

Значение 0x38F - это адрес регистра IA32_PERF_GLOBAL_CTRL MSR и опции -aуказывает, что инструкция rdmsr должна выполняться на всех логических ядрах.По умолчанию это должно вывести 7000000ff (когда HT отключен) или 70000000f (когда HT включен) для всех логических ядер.Для счетчика производительности с фиксированной функцией INST_RETIRED.ANY бит в индексе 32 является тем, который его разрешает, поэтому он должен быть равен 1. Значение 7000000ff, что все три счетчика с фиксированной функцией и все восемь программируемых счетчиков

Регистр IA32_PERF_GLOBAL_CTRL имеет один бит разрешения для каждого счетчика производительности на логическое ядро.Каждый программируемый счетчик производительности также имеет свой собственный регистр управления и есть регистр управления для всех счетчиков с фиксированной функцией.В частности, регистр управления для счетчика производительности с фиксированной функцией INST_RETIRED.ANY равен IA32_FIXED_CTR_CTRL, схема которого показана на рис. 18-7 тома Intel Manual Volume 3. В регистре 12 определенных битов, первые 4 битаможет использоваться для управления поведением первого счетчика с фиксированной функцией, т. е. INST_RETIRED.ANY (порядок показан в таблице 19-2).Перед изменением регистра вы должны сначала проверить, как он был инициализирован ОС, выполнив:

sudo rdmsr -a 0x38D

По умолчанию он должен вывести 0xb0.Это указывает на то, что второй счетчик с фиксированной функцией (циклы без остановок) включен и настроен для подсчета в режиме супервизора и в режиме пользователя.Чтобы включить INST_RETIRED.ANY и настроить его на подсчет только событий пользовательского режима, сохраняя при этом счетчик циклов неинсталлированного ядра как есть, выполните следующую команду:

sudo wrmsr -a 0x38D 0xb2

После выполнения этой команды события сразу подсчитываются.Вы можете проверить это, прочитав первый счетчик фиксированных функций IA32_PERF_FIXED_CTR0 (см. Таблицу 19-2):

sudo rdmsr -a 0x309

Вы можете выполнить эту команду несколько раз и посмотреть, как меняется счет на каждом ядре.К сожалению, это означает, что к моменту запуска вашей программы текущее значение в IA32_PERF_FIXED_CTR0 будет в основном случайным.Вы можете попытаться сбросить счетчик, выполнив:

sudo wrmsr -a 0x309 0

Но фундаментальная проблема остается;вы не можете мгновенно сбросить счетчик и запустить вашу программу.Как указано в ответе @ Peter, правильный способ использования любого счетчика производительности - заключить область интереса между rdpmc инструкциями и принять разницу.

Модуль ядра MSR очень удобен, поскольку единственный способДоступ к регистрам MSR осуществляется в режиме ядра.Однако существует альтернатива переносу кода между rdpmc инструкциями.Вы можете написать свой собственный модуль ядра и поместить свой код в модуль ядра сразу после инструкции, которая включает счетчик.Вы даже можете отключить прерывания.Как правило, этот уровень точности не стоит усилий.

Вы можете использовать опцию -p вместо -a, чтобы указать конкретное логическое ядро.Однако вам нужно убедиться, что программа запущена на том же ядре с taskset -c 3 ./a.out для запуска на ядре # 3, например.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...