Сборка - Как оценить инструкцию процессора по задержке и пропускной способности - PullRequest
0 голосов
/ 10 сентября 2018

Я ищу тип формулы / способа измерения скорости выполнения инструкции или более точного определения «оценки» каждой инструкции по циклам ЦП.

Давайте возьмем следующую программу сборки для примера,

nop                     
mov         eax,dword ptr [rbp+34h] 
inc         eax     
mov         dword ptr [rbp+34h],eax  

и следующая информация о Intel Skylake:

mov r, m: пропускная способность = 0,5, задержка = 2

мов м, р : Пропускная способность = 1 задержка = 2

nop: пропускная способность = 0,25 латентность = не

вкл .: пропускная способность = 0,25 латентность = 1

Я знаю, что порядок инструкций в программе имеет значение здесь, но Я стремлюсь создать что-то общее, что не должно быть "точным в одном цикле"

Кто-нибудь знает, как я могу это сделать?

Большое спасибо

1 Ответ

0 голосов
/ 10 сентября 2018

Нет формулы, которую вы можете применить;Вы должны измерить .

Одна и та же инструкция на разных версиях одного и того же семейства uarch может иметь разную производительность.например, mulps:

  • Пропускная способность 1c / 5c / латентность. Sandybridge.
  • HSW 0,5 / 5. BDW 0,5 / 3 (более быстрый путь умножения в блоке FMA? FMA по-прежнему 5c).
  • SKL 0,5 / 4 (также FMA с более низкой задержкой).SKL также запускает addps на блоке FMA, отбрасывая выделенный блок умножения FP, поэтому добавленная задержка выше, но пропускная способность выше.

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


Вы измеряете, повторяя инструкцию много раз в развернутом цикле.Или полностью развернутый без зацикливания, но затем вы победите uop-кеш и можете получить узкие места в интерфейсе.(например, для декодирования 10-байтовых mov r64, imm64)

Агнер Фог создает свои таблицы команд (которые вы, кажется, читаете), синхронизируя большие блоки кода, которые не повторяются, повторяя инструкцию.https://agner.org/optimize/. Вступительный раздел его таблиц инструкций кратко объясняет, как он измеряет, и в его руководстве по микроархам объясняется больше деталей того, как разные микроархитектуры x86 работают внутри.

http://instlatx64.atw.hu/ также дает результатыэкспериментальных измерений.Я думаю, что они используют похожую технику для большого блока с одной и той же повторяющейся инструкцией, возможно, достаточно маленькой, чтобы поместиться в кэш uop.Но они не используют счетчики производительности для измерения того, какой порт выполнения необходим каждой инструкции, поэтому их пропускная способность не помогает вам определить, какие инструкции конкурируют с какими другими инструкциями.


Для измеренияЗадерживая себя, вы делаете вывод каждой инструкции входным для следующей.

 mov  ecx, 10000000
 inc_latency:
     inc eax
     inc eax
     inc eax
     inc eax
     inc eax
     inc eax

     sub ecx,1          ; avoid partial-flag false dep for P4
     jnz inc_latency    ; dec or sub/jnz macro-fuses into 1 uop on Intel SnB-family

Эта цепочка зависимостей из 7 inc инструкций будет узким местом цикла с 1 итерацией на 7 * inc_latency циклов.Используя счетчики perf для тактов ядра (не циклов RDTSC), вы можете легко измерить время для всех итераций до 1 части в 10k, и с большей осторожностью, возможно, даже более точно, чем это.Счетчик повторений 10000000 скрывает время запуска / остановки независимо от того, какое время вы используете.

Обычно я помещаю такой цикл в статический исполняемый файл Linux, который просто делает системный вызов sys_exit(0) напрямую (с syscall) и время всего исполняемого файла с perf stat ./testloop, чтобы получить время и количество циклов.(См. Может ли MOV x86 действительно быть "свободным"? Почему я вообще не могу воспроизвести это? для примера).

Другой пример - Понимание влияния lfence нацикл с двумя длинными цепочками зависимостей, для увеличения длины , с дополнительным усложнением использования lfence для опустошения окна выполнения не по порядку для двух цепочек dep.


Чтобы измерить пропускную способность, вы используете отдельные регистры и / или время от времени включаете обнуление xor, чтобы разорвать цепочки dep и позволить exec-of-order exec перекрывать вещи. Не забудьте также использовать счетчики perf, чтобы увидеть, какие портыон может работать, поэтому вы можете определить, с какими другими инструкциями он будет конкурировать.(Например, FMA (p01) и shuffles (p5) вообще не конкурируют за внутренние ресурсы в Haswell / Skylake, только за пропускную способность внешнего интерфейса.) Не забудьте также измерить количество входных операций переднего плана: некоторыеинструкции декодируют для умножения мопов.

Сколько разных цепочек зависимостей нам нужно, чтобы избежать узкого места?Ну, мы знаем задержку (сначала измерим ее) и знаем максимально возможную пропускную способность (количество портов выполнения или пропускную способность интерфейса).

Например, если FP умножение имеет пропускную способность 0,25 с (4 за такт), мы можем сохранить 20 в полете сразу на Haswell (задержка 5 с). Это больше, чем у нас есть регистры, поэтому мы могли бы просто использовать все 16 и обнаружить, что на самом деле пропускная способность составляет всего 0,5 с. Но если бы выяснилось, что 16 регистров являются узким местом, мы могли бы время от времени добавлять xorps xmm0,xmm0 и допускать, чтобы неупорядоченное выполнение перекрывало некоторые блоки.

Чем больше, тем лучше; едва достаточное количество, чтобы скрыть задержку, может замедлиться из-за несовершенного планирования. Если бы мы хотели сходить с ума, измеряя inc, мы бы сделали это:

 mov  ecx, 10000000
 inc_latency:
   %rep 10          ;; source-level repeat of a block, no runtime branching
     inc eax
     inc ebx
     ; not ecx, we're using it as a loop counter
     inc edx
     inc esi
     inc edi
     inc ebp
     inc r8d
     inc r9d
     inc r10d
     inc r11d
     inc r12d
     inc r13d
     inc r14d
     inc r15d
   %endrep

     sub ecx,1          ; break partial-flag false dep for P4
     jnz inc_latency    ; dec/jnz macro-fuses into 1 uop on Intel SnB-family

Если бы нас беспокоили ложные зависимости частичных флагов или эффекты слияния флагов, мы могли бы поэкспериментировать с микшированием где-нибудь в xor eax,eax, чтобы OoO exec перекрывались больше, чем просто когда sub записывал все флаги. (См. Инструкцию INC против ADD 1: имеет ли это значение? )

Существует аналогичная проблема для измерения пропускной способности и задержки shl r32, cl в семействе Sandybridge: цепочка зависимостей флагов обычно не актуальна для вычислений, но установка shl вплотную создает зависимость через FLAGS как ну как через реестр. (Или для пропускной способности, даже нет регистра dep).

Я написал об этом в блоге Агнера Фога: https://www.agner.org/optimize/blog/read.php?i=415#860. Я смешал shl edx,cl с четырьмя add edx,1 инструкциями, чтобы увидеть, какое постепенное замедление было добавлением еще одной инструкции, где зависимость FLAGS была не вопрос. В SKL он замедляется только на дополнительные 1,23 цикла в среднем, поэтому истинная стоимость задержки для этого shl составила всего ~ 1,23 цикла, а не 2. (Это не целое число или только 1 из-за конфликтов ресурсов для запуска Полагаю, что слияние флагов shl, наверное, BMI2 shlx edx, edx, ecx будет ровно 1с, потому что это всего лишь один моп.)


Связанные: для статического анализа производительности целых блоков кода (содержащих различные инструкции) см. Какие соображения относятся к прогнозированию задержки для операций на современных суперскалярных процессорах и как я могу вычислить их вручную? . (Он использует слово «задержка» для сквозной задержки всего вычисления, но на самом деле спрашивает о вещах, достаточно малых для того, чтобы OoO exec перекрывал разные части, поэтому задержка команд и пропускная способность имеют значение.)


Числа Latency=2 для загрузки / хранения, по-видимому, взяты из таблиц инструкций Agner Fog (https://agner.org/optimize/). Они, к сожалению, не точны для цепочки mov rax, [rax]. Вы обнаружите, что это 4c задержка, если вы измеряете ее, помещая это в цикл.

Агнер разбивает задержку загрузки / хранилища на что-то, из-за чего общая задержка хранения / перезагрузки получается правильной, но по какой-то причине он не делает загрузочную часть равной задержке загрузки-использования L1d, когда она приходит из кеша буфера магазина. (Но также учтите, что если загрузка передает инструкцию ALU вместо другой загрузки, задержка составляет 5 с. Таким образом, быстрый путь в простом режиме адресации помогает только для чистого отслеживания указателя.)

...