RDTSCP в NASM всегда возвращает одно и то же значение - PullRequest
0 голосов
/ 11 февраля 2019

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

Я прочитал «Как оценить время выполнения кода на архитектурах наборов команд Intel IA-32 и IA-64» Габриэле Паолони (Intel, сентябрь 2010 г.) и другие веб-ресурсы (большинство из которых были примерами на C).,

Используя приведенный ниже код (перевод с C), я тестирую различные инструкции, но RDTSCP всегда возвращает ноль в RDX и 7 в RAX.Сначала я подумал, что 7 - это количество циклов, но, очевидно, не все инструкции занимают 7 циклов.

rdtsc
cpuid
addsd xmm14,xmm1 ; Instruction to time
rdtscp
cpuid

Возвращает 7, что неудивительно, потому что на некоторых архитектурах addd составляет 7 циклов с включенной задержкой.Первые две инструкции могут (по некоторым данным) быть обратными, сначала cpuid, а затем rdtsc, но здесь это не имеет значения.

Когда я меняю инструкцию на 2-тактную инструкцию:

rdtsc
cpuid
add rcx,rdx ; Instruction to time
rdtscp
cpuid

Это также возвращает 7 в rax и ноль в rdx.

Итак, мои вопросы:

  1. Как получить доступ и интерпретировать значения, возвращенные в RDX: RAX?

  2. Почемувсегда ли RDX возвращает ноль и что он должен возвращать?

ОБНОВЛЕНИЕ:

Если я изменю код на это:

cpuid
rdtsc
mov [start_time],rax
addsd xmm14,xmm1 ; INSTRUCTION
rdtscp
mov [end_time],rax
cpuid
mov rax,[end_time]
mov rdx,[start_time]
sub rax,rdx

Я получаю 64 в Rax, но это звучит как слишком много циклов.

1 Ответ

0 голосов
/ 11 февраля 2019

Ваш первый код (ведущий к заглавному вопросу) содержит ошибки, поскольку он перезаписывает результаты rdtsc и rdtscp с результатами cpuid в EAX, EBX, ECX и EDX.

Используйте lfence вместо cpuid;на Intel с тех пор, как навсегда, и AMD с включенным смягчением Spectre, lfence будет сериализовать поток команд и, таким образом, делать то, что вы хотите с rdtsc.


Помните, что RDTSC считает контрольные циклы, а нетактовые частоты ядра. Получить счетчик тактов ЦП? для этого и больше о RDTSC.

В вашем интервале измерений нет cpuid или lfence.Но вы делаете сами rdtscp в интервале измерений.Спина к спине rdtscp не является быстрой, 64 эталонных циклов звучат вполне разумно, если вы работали без прогрева процессора.Частота холостого хода обычно намного ниже, чем у эталонного цикла ;1 эталонный цикл равен или близок к частоте "стикера", например, максимальная частота без поддержки турбо, на процессорах Intel.например, 4008 МГц на процессоре Skylake "4 ГГц".


Это не то, как вы рассчитываете одну инструкцию

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

Однако вы можете попытаться вычесть накладные расходы на измерения.например, clflush для аннулирования строки кэша с помощью функции C .И смотрите также следующие действия: Использование счетчика меток времени и clock_gettime для пропуска кэша и Измерение задержки памяти со счетчиком меток времени .


Эточто я обычно использую, чтобы профилировать задержку или пропускную способность (и uops слитый и неиспользуемый домен) инструкции короткого блока .Отрегулируйте, как вы используете его, чтобы ограничить задержку, как здесь, или нет, если вы хотите просто проверить пропускную способность.например, с блоком %rep с достаточным количеством различных регистров, чтобы скрыть задержку, или разорвать цепочки зависимостей с помощью pxor xmm3, xmm3 после короткого блока и позволить exec-of-order exec работать своим волшебством.(До тех пор, пока вы не станете узким местом в интерфейсе.)

Возможно, вы захотите использовать пакет smartalign NASM или YASM, чтобы избежать стены однобайтовых инструкций NOP для директивы ALIGN.NASM по умолчанию настроен на действительно глупые NOP даже в 64-битном режиме, где всегда поддерживается long-NOP.

global _start
_start:
    mov   ecx, 1000000000
; linux static executables start with XMM0..15 already zeroed
align 32                     ; just for good measure to avoid uop-cache effects
.loop:
    ;; LOOP BODY, put whatever you want to time in here
    times 4   addsd  xmm4, xmm3

    dec   ecx
    jnz   .loop

    mov  eax, 231
    xor  edi, edi
    syscall          ; x86-64 Linux sys_exit_group(0)

Запустите его с чем-то вроде этого с одним вкладышем, который связывает его в статический исполняемый файл и профилирует его с помощью perf stat, , который вы можете стрелять вверх и перезапускать каждый раз, когда вы меняете источник :

(я фактически помещаю опциональную дизассемблирование nasm + ld + в сценарий оболочки под названием asm-link, чтобы сохранить ввод, когда я не профилирую. Разборка гарантирует, что в вашем цикле находится то, что вы имели в виду для профилирования, особенно если в вашем коде есть что-то %if.на вашем терминале прямо перед профилем, если вы хотите прокрутить назад при тестировании теорий в своей голове.)

t=testloop; nasm -felf64 -g "$t.asm" && ld "$t.o" -o "$t" &&  objdump -drwC -Mintel "$t" &&
 taskset -c 3 perf stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,branches,instructions,uops_issued.any,uops_executed.thread -r4 ./"$t"

Результат от i7-6700k при 3,9 ГГц (текущий perfимеет ошибку отображения масштаба модуля для вторичного столбца. Она исправлена ​​в восходящем направлении, но Arch Linux еще не обновился.):

 Performance counter stats for './testloop' (4 runs):

          4,106.09 msec task-clock                #    1.000 CPUs utilized            ( +-  0.01% )
                17      context-switches          #    4.080 M/sec                    ( +-  5.65% )
                 0      cpu-migrations            #    0.000 K/sec                  
                 2      page-faults               #    0.487 M/sec                  
    16,012,778,144      cycles                    # 3900323.504 GHz                   ( +-  0.01% )
     1,001,537,894      branches                  # 243950284.862 M/sec               ( +-  0.00% )
     6,008,071,198      instructions              #    0.38  insn per cycle           ( +-  0.00% )
     5,013,366,769      uops_issued.any           # 1221134275.667 M/sec              ( +-  0.01% )
     5,013,217,655      uops_executed.thread      # 1221097955.182 M/sec              ( +-  0.01% )

          4.106283 +- 0.000536 seconds time elapsed  ( +-  0.01% )

На моем i7-6700k (Skylake), addsd имеет 4 циклазадержка, пропускная способность 0,5 с.(т.е. 2 за час, если задержка не была узким местом).См. https://agner.org/optimize/, https://uops.info/, и http://instlatx64.atw.hu/.

16 циклов на ветвь = 16 циклов на цепочку 4 addsd = задержка 4 цикла для addsd, воспроизведениеAgner Fog измеряет 4 цикла с точностью до 1 части на 100 даже для этого теста, который включает незначительные накладные расходы при запуске и издержки прерывания.

Выберите различные счетчики для записи.При добавлении :u, например instructions:u, к perf даже будут учитываться только инструкции пользовательского пространства, исключая те, которые выполнялись во время обработчиков прерываний.Обычно я этого не делаю, поэтому я могу рассматривать эти накладные расходы как часть объяснения времени настенных часов.Но если вы это сделаете, cycles:u может сопоставить очень близко с instructions:u.

-r4 запускает его 4 раза и усредняет, что может быть полезно, чтобы увидеть, если есть многовариация между циклами вместо простого получения среднего значения из более высокого значения в ECX.

Настройте исходное значение ECX так, чтобы общее время составляло от 0,1 до 1 секунды, что обычно достаточно, особенно если ваш процессор ускоряетсядо макс. турбо очень быстро (например, Skylake с аппаратными P-состояниями и довольно агрессивной energy_performance_preference).Или макс. Без турбо с отключенным турбо.

Но это учитывается в тактах ядра, а не в эталонных циклах, поэтому он все равно дает тот же результат независимо от изменений частоты процессора .(+ - некоторый шум от остановки часов во время перехода.)

...