Как именно работают частичные регистры на Haswell/Skylake? Написание AL кажется ложной зависимостью от RAX, а AH несовместимо

Этот цикл выполняется на одной итерации за 3 цикла на Intel Conroe/Merom, в узком месте imul пропускная способность, как и ожидалось. Но на Haswell/Skylake он выполняется за одну итерацию за 11 циклов, по-видимому, потому что setnz al зависит от последнего imul,

; synthetic micro-benchmark to test partial-register renaming
    mov     ecx, 1000000000
.loop:                 ; do{
    imul    eax, eax     ; a dep chain with high latency but also high throughput
    imul    eax, eax
    imul    eax, eax

    dec     ecx          ; set ZF, independent of old ZF.  (Use sub ecx,1 on Silvermont/KNL or P4)
    setnz   al           ; ****** Does this depend on RAX as well as ZF?
    movzx   eax, al
    jnz  .loop         ; }while(ecx);

Если setnz al зависит от rax последовательность 3ximul/setcc/movzx образует цепочку зависимостей, переносимых циклом. Если нет, каждый setcc / movzx / 3x imul цепь является независимой, разветвленной от dec это обновляет счетчик цикла. Значение 11 c на итерацию, измеренное в HSW/SKL, прекрасно объясняется узким местом задержки: 3x3c(imul) + 1c(чтение-изменение-запись с помощью setcc) + 1c(movzx в том же регистре).


Не по теме: избегать этих (преднамеренных) узких мест

Я стремился к понятному / предсказуемому поведению, чтобы изолировать частично-reg вещи, не оптимальную производительность.

Например, xor -золь / set-flags / setcc лучше в любом случае (в этом случае xor eax,eax / dec ecx / setnz al). Это устраняет необходимость в eax на всех процессорах (кроме ранних семейств P6, таких как PII и PIII), по-прежнему позволяет избежать штрафов за частичное объединение регистров и сохраняет 1с movzx задержка. Он также использует меньшее количество ALU uop на процессорах, которые обрабатывают обнуление xor на этапе переименования регистра. Посмотрите эту ссылку, чтобы узнать больше об использовании обнуления с помощью xor setcc,

Обратите внимание, что AMD, Intel Silvermont/KNL и P4 вообще не выполняют частичное переименование регистров. Это только особенность в процессорах семейства Intel P6 и его потомке, семействе Intel Sandybridge, но, похоже, постепенно прекращается.

GCC, к сожалению, имеет тенденцию использовать cmp / setcc al / movzx eax,al где он мог бы использовать xor вместо movzx (Пример компилятора-компилятора Godbolt), в то время как clang использует xor-zero/cmp/setcc, если вы не объедините несколько логических условий, таких как count += (a==b) | (a==~b),

Версия xor/dec/setnz работает при 3.0c на итерацию для Skylake, Haswell и Core2 (узкое место на imul пропускная способность). xor Обнуление ломает зависимость от старого значения eax на всех процессорах, вышедших из строя, кроме PPro/PII/PIII/early-Pentium-M (где он по-прежнему избегает штрафов за слияние с частичным регистром, но не нарушает dep). Микроархист Агнера Фога описывает это. Замена обнуления на xor mov eax,0 замедляет его до одного на 4,78 цикла на Core2: 2-3c срыв (во внешнем интерфейсе?) для вставки частичного рег слияния uop, когда imul читает eax после setnz al,

Также я использовал movzx eax, al который побеждает MOV-ликвидации, так же, как mov rax,rax делает. (IvB, HSW и SKL можно переименовать movzx eax, bl с задержкой 0, но Core2 не может). Это делает все равным в Core2 / SKL, за исключением поведения частичного регистра.


Поведение Core2 соответствует руководству по микроархам Agner Fog, но поведение HSW/SKL - нет. Из раздела 11.10 для Skylake и так же для предыдущих Intel Intel:

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

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

Агнер действительно описывает объединяющуюся меру, вставляемую (без остановки) для регистров с высоким8 (AH/BH/CH/DH) на Sandybridge через Skylake и для low8/low16 на SnB. (К сожалению, в прошлом я распространял неверную информацию и говорил, что Haswell может объединить AH бесплатно. Я просмотрел раздел Hasner Агнера слишком быстро и не заметил последующий параграф о регистрах high8. Дайте мне знать, если вы видите мои неправильные комментарии к другим сообщениям, поэтому я могу удалить их или добавить исправление. Я постараюсь хотя бы найти и отредактировать свои ответы там, где я это сказал.)


Мои актуальные вопросы: как именно частичные регистры действительно ведут себя на Skylake?

Все ли одинаково от IvyBridge до Skylake, включая дополнительную задержку high8?

Руководство по оптимизации Intel не содержит конкретных указаний о том, какие процессоры имеют ложные зависимости для чего (хотя в нем упоминается, что некоторые процессоры имеют их), и не учитывает такие вещи, как чтение AH/BH/CH/DH (регистры high8), добавляя дополнительную задержку, даже если они не был изменен.

Если есть какое-либо поведение семейства P6 (Core2/Nehalem), которое не описано в руководстве по микроархам Agner Fog, это также было бы интересно, но я, вероятно, должен ограничить сферу этого вопроса только Skylake или Sandybridge-family.


Мои данные теста Skylake, от сдачи %rep 4 короткие последовательности внутри небольшого dec ebp/jnz цикл, который запускает 100M или 1G итераций. Я измерял циклы с Linux perf так же, как в моем ответе здесь, на том же оборудовании (настольный Skylake i7 6700k).

Если не указано иное, каждая инструкция выполняется как 1 uop слитых доменов, используя порт выполнения ALU. (Измерено с ocperf.py stat -e ...,uops_issued.any,uops_executed.thread). Это обнаруживает (отсутствие) mov-elvention и лишних слияний.

Случаи "4 за цикл" являются экстраполяцией к бесконечно развернутому случаю. Накладные расходы цикла занимают некоторую часть полосы пропускания внешнего интерфейса, но все, что лучше, чем 1 на цикл, указывает на то, что переименование регистра позволило избежать зависимости вывода записи после записи и что внутренняя операция не обрабатывается внутренне как чтение-изменение -записывать.

Запись только в AH: предотвращает выполнение цикла из буфера обратной связи (также известного как Loop Stream Detector (LSD)). Рассчитывает на lsd.uops равны 0 для HSW и малы для SKL (около 1,8 тыс.) и не масштабируются с помощью счетчика итераций цикла. Вероятно, эти цифры взяты из некоторого кода ядра. Когда петли бегут от ЛСД, lsd.uops ~= uops_issued с точностью до шума измерения. Некоторые циклы чередуются между LSD или без-LSD (например, когда они могут не помещаться в кэш UOP, если декодирование начинается не в том месте), но я не сталкивался с этим при тестировании.

  • повторный mov ah, bh и / или mov ah, bl работает на 4 за цикл. Это занимает ALU UOP, поэтому он не устраняется, как mov eax, ebx является.
  • повторный mov ah, [rsi] работает на 2 за цикл (узкое место пропускной способности нагрузки).
  • повторный mov ah, 123 работает на 1 за цикл. ( Разрушительный xor eax,eax внутри петли убирает узкое место.)
  • повторный setz ah или же setc ah работает на 1 за цикл. (Разрушительный xor eax,eax позволяет узкое место на пропускной способности p06 для setcc и ветвь петли.)

    Почему пишу ah с инструкцией, которая обычно использует исполнительный блок ALU, имеет ложную зависимость от старого значения, в то время как mov r8, r/m8 нет (для рег или памяти)? (И что насчет mov r/m8, r8? Конечно, не имеет значения, какой из двух операционных кодов вы используете для ходов reg-reg?)

  • повторный add ah, 123 работает на 1 за цикл, как и ожидалось.

  • повторный add dh, cl работает на 1 за цикл.
  • повторный add dh, dh работает на 1 за цикл.
  • повторный add dh, ch работает на 0,5 за цикл. Чтение [ABCD]H особенное, когда они "чистые" (в этом случае RCX совсем недавно не модифицировался).

Терминология: все они оставляют AH (или DH) " грязным ", то есть нуждаются в слиянии (с объединяющим оператором), когда читается остальная часть регистра (или в некоторых других случаях). то есть, что AH переименован отдельно от RAX, если я правильно понимаю. " чистый " - это наоборот. Есть много способов очистить грязный регистр, самый простой способ inc eax или же mov eax, esi,

Запись только в AL: эти циклы запускаются из LSD: uops_issue.any ~ = lsd.uops,

  • повторный mov al, bl работает на 1 за цикл. Случайный взлом xor eax,eax для каждой группы позволяет ООО выполнять узкие места по пропускной способности, а не по задержке.
  • повторный mov al, [rsi] работает на 1 за цикл, как микроплавкий ALU+ нагрузка моп. (uops_issued=4G + издержки цикла, uops_executed=8G + издержки цикла). Разрушительный xor eax,eax прежде чем группа из 4 позволяет узкое место на 2 нагрузки за такт.
  • повторный mov al, 123 работает на 1 за цикл.
  • повторный mov al, bh работает на 0,5 за цикл. (1 на 2 цикла). Чтение [ABCD]H особенное.
  • xor eax,eax + 6x mov al,bh + dec ebp/jnz: 2c на итерацию, узкое место по 4 мопа за часы для внешнего интерфейса.
  • повторный add dl, ch работает на 0,5 за цикл. (1 на 2 цикла). Чтение [ABCD]H, очевидно, создает дополнительную задержку для dl,
  • повторный add dl, cl работает на 1 за цикл.

Я думаю, что запись в регистр с низким 8 ведет себя как смесь RMW в полный регистр, как add eax, 123 было бы, но это не вызывает слияние, если ah грязный. Так (кроме игнорирования AH слияние) он ведет себя так же, как и на процессорах, которые вообще не выполняют частичное переименование. Похоже на то AL никогда не переименовывается отдельно от RAX?

  • inc al / inc ah пары могут работать параллельно.
  • mov ecx, eax вставляет объединяющий UOP, если ah "грязный", но фактический mov переименован Это то, что Агнер Фог описывает для IvyBridge и позже.
  • повторный movzx eax, ah работает по одному на 2 цикла. (Чтение старших регистров после записи полных регистров имеет дополнительную задержку.)
  • movzx ecx, al имеет нулевую задержку и не использует порт выполнения в HSW и SKL. (Подобно тому, что Агнер Фог описывает для IvyBridge, но он говорит, что HSW не переименовывает movzx).
  • movzx ecx, cl имеет задержку 1С и принимает порт выполнения. ( Mov-ликвидация никогда не работает для same,same случай, только между разными архитектурными регистрами.)

    Цикл, который вставляет объединяющий элемент в каждую итерацию, не может запускаться из LSD (буфер цикла)?

Я не думаю, что есть что-то особенное в AL/AH/RAX против B*, C*, DL/DH/RDX. Я проверил некоторые с частичными регистрами в других регистрах (хотя я в основном показываю AL / AH для согласованности) и никогда не замечал никакой разницы.

Как мы можем объяснить все эти наблюдения разумной моделью того, как микроархит работает внутри?


Связанный: проблемы частичного флага отличаются от проблем частичного регистра. Смотрите инструкцию INC против ADD 1: это имеет значение? для некоторых супер-странных вещей с shr r32,cl (и даже shr r32,2 на Core2/Nehalem: не читайте флаги от смены, кроме 1).

См. Также Проблемы с ADC/SBB и INC/DEC в тесных циклах на некоторых процессорах для частичной пометки вещей в adc петли.

1 ответ

Решение

Другие ответы приветствуются, чтобы обратиться к Sandybridge и IvyBridge более подробно. У меня нет доступа к этому оборудованию.


Я не обнаружил каких-либо различий в поведении с частичной регистрацией между HSW и SKL. На Haswell и Skylake все, что я до сих пор тестировал, поддерживает эту модель:

AL никогда не переименовывается отдельно от RAX (или r15b от r15). Поэтому, если вы никогда не касаетесь регистров high8 (AH/BH/CH/DH), все ведет себя точно так же, как на процессоре без частичного переименования (например, AMD).

Доступ только для записи к AL сливается с RAX с зависимостью от RAX. Для загрузок в AL это загрузочный урок ALU+ с микроплавлением, который выполняется на p0156, что является одним из самых убедительных доказательств того, что он действительно объединяется при каждой записи, а не просто выполняет какую-то сложную двойную бухгалтерию, как предположил Агнер.

Агнер (и Intel) говорят, что для Sandybridge может потребоваться объединенная мера для AL, поэтому он, вероятно, переименован отдельно от RAX. Для SnB в руководстве по оптимизации Intel (раздел 3.5.2.4 Частичные регистры) указано

SnB (не обязательно более поздняя версия) вставляет объединяющую меру в следующих случаях:

  • После записи в один из регистров AH, BH, CH или DH и перед последующим чтением 2-, 4- или 8-байтовой формы того же регистра. В этих случаях вводится микрооперация слияния. Вставка использует полный цикл выделения, в котором другие микрооперации не могут быть распределены.

  • После микрооперации с регистром назначения 1 или 2 байта, который не является источником инструкции (или более крупной формы регистра), и перед последующим чтением 2-, 4- или 8-байтовой формы тот же регистр. В этих случаях микрооперация слияния является частью потока.

Я думаю, что они говорят это на SnB, add al,bl будет RMW полный RAX вместо того, чтобы переименовывать его отдельно, потому что один из исходных регистров (часть) RAX. Я думаю, что это не относится к такой нагрузке, как mov al, [rbx + rax]; rax в режиме адресации, вероятно, не считается источником.

Я не проверял, должны ли high8 слияния все еще самостоятельно выпускать / переименовывать в HSW/SKL. Это сделало бы фронтальное воздействие эквивалентным 4 мопам (так как это проблема / переименование ширины конвейера).

  • Нет способа разорвать зависимость с AL без написания EAX/RAX. xor al,al не помогает, и не помогает mov al, 0,
  • movzx ebx, al имеет нулевую задержку (переименована) и не нуждается в исполнительном блоке. (то есть работы по устранению мов на HSW и SKL). Он вызывает слияние AH, если он грязный, что, я думаю, необходимо для его работы без ALU. Вероятно, это не случайно, что Intel отказалась от переименования low8 в том же Uarch, который ввел mov-elission. (В микроархиве Агнера Фога есть ошибка, в которой говорится, что движения с нулевым расширением не исключаются в HSW или SKL, только в IvB.)
  • movzx eax, al не устраняется при переименовании. MOV-ликвидации на Intel никогда не работает на то же самое, то же самое. mov rax,rax также не исключается, даже если не нужно ничего расширять. (Хотя не было бы никакого смысла давать ему специальную аппаратную поддержку, потому что это просто не работает, в отличие от mov eax,eax). В любом случае, при расширении нуля предпочитайте перемещаться между двумя отдельными архитектурными регистрами, будь то с 32-разрядным mov или 8-битный movzx,
  • movzx eax, bx не устраняется при переименовании на HSW или SKL. Он имеет задержку 1С и использует ALU UOP. Руководство по оптимизации Intel упоминает только нулевую задержку для 8-битного movzx (и указывает, что movzx r32, high8 никогда не переименовывается).

Reg-8 с высокими значениями могут быть переименованы отдельно от остальной части регистра, и они действительно должны объединяться.

  • Доступ только для записи ah с mov ah, r8 или же mov ah, [mem] переименуйте AH, без зависимости от старого значения. Обе эти инструкции обычно не требуют ALU UOP (для 32-разрядной версии).
  • RMW AH (как inc ah Грязи это.
  • setcc ah зависит от старого ah, но все еще грязные это. Я думаю mov ah, imm8 то же самое, но не проверял так много угловых случаев.

    (Необъяснимо: цикл с участием setcc ah иногда можно запустить от ЛСД, см. rcr цикл в конце этого поста. Может быть, пока ah чист в конце цикла, он может использовать ЛСД?).

    Если ah грязный, setcc ah сливается с переименованным ah вместо принудительного слияния в rax, например %rep 4 (inc al / test ebx,ebx / setcc ah / inc al / inc ah) не генерирует мопов слияния и работает только в 8.7c (задержка 8 inc al замедлен конфликтами ресурсов от мопов для ah, Так же inc ah / setcc ah деп цепи).

    Я думаю, что здесь происходит, что setcc r8 всегда реализуется как чтение-изменение-запись. Intel, вероятно, решила, что не стоит иметь только запись setcc моп, чтобы оптимизировать setcc ah случай, так как сгенерированный компилятором код очень редко setcc ah, (Но см. Ссылку в вопросе: clang4.0 с -m32 будет делать так.)

  • Чтение AX, EAX или RAX запускает объединение (которое занимает внешнюю проблему / переименовывает пропускную способность). Вероятно, RAT (таблица распределения регистров) отслеживает состояние с высоким уровнем загрязнения для архитектурного R[ABCD]X, и даже после того, как запись в AH прекращается, данные AH сохраняются в отдельном физическом регистре от RAX. Даже с 256 NOP между записью AH и чтением EAX, существует дополнительный слияние. (Размер ROB =224 по SKL, так что это гарантирует, что mov ah, 123 был в отставке). Обнаружено с помощью uops_issued/execute счетчиков перфорации, которые четко показывают разницу.

  • Чтение-изменение-запись AL (например, inc al) сливается бесплатно, как часть ALU uop. (Только проверено с несколькими простыми мопами, как add / inc не div r8 или же mul r8). Опять же, слияние не происходит, даже если AH грязный.

  • Только для записи в EAX/RAX (например, lea eax, [rsi + rcx] или же xor eax,eax) очищает грязное состояние AH (без слияния).

  • Только для записи в AX (mov ax, 1) запускает слияние AH первым. Я думаю, что вместо специального случая это работает как любой другой RMW AX/RAX. (ТОДО: тест mov ax, bx, хотя это не должно быть особенным, потому что это не переименовано.)
  • xor ah,ah имеет задержку 1с, не прерывает работу и все еще нуждается в порте выполнения.
  • Чтение и / или запись AL не вызывает слияния, поэтому AH может оставаться грязным (и использоваться независимо в отдельной цепочке развертывания). (например add ah, cl / add al, dl может работать по 1 за час (узкое место при дополнительной задержке).

Загрязнение AH предотвращает запуск цикла из LSD (буфера цикла), даже если нет слияний. LSD - это когда процессор перезагружает мопы в очереди, которая передает этап выпуска / переименования. (Называется IDQ).

Вставка объединяющих мопов немного похожа на вставку стековых синхронизирующих мопов для механизма стеков. Руководство по оптимизации Intel гласит, что LSD SnB не может запускать циклы с несовпадающими push / pop, что имеет смысл, но это означает, что он может запускать циклы со сбалансированным push / pop, Это не то, что я вижу на SKL: даже сбалансированный push / pop предотвращает бег от ЛСД (например, push rax / pop rdx / times 6 imul rax, rdx, (Может существовать реальная разница между LSD и HSW / SKL в SnB: SnB может просто "заблокировать" мопы в IDQ вместо того, чтобы повторять их несколько раз, поэтому цикл из 5 мопов выдает 2 цикла вместо 1.25.) В любом случае, кажется, что HSW / SKL не может использовать LSD, когда регистр старшего разряда загрязнен или когда он содержит мопы стекового механизма.

Такое поведение может быть связано с ошибкой в ​​SKL:

SKL150: короткие циклы, в которых используются регистры AH / BH / CH / DH, могут вызвать непредсказуемое поведение системы

Проблема: В сложных микроархитектурных условиях короткие циклы из менее чем 64 команд, которые используют регистры AH, BH, CH или DH, а также соответствующие им более широкие регистры (например, RAX, EAX или AX для AH), могут вызвать непредсказуемое поведение системы., Это может произойти, только если оба логических процессора на одном физическом процессоре активны.

Это также может быть связано с инструкцией Intel по оптимизации, согласно которой SnB должен, по крайней мере, самостоятельно выпускать / переименовывать AH-merge uop в цикле. Это странная разница для внешнего интерфейса.

Мой журнал ядра Linux говорит microcode: sig=0x506e3, pf=0x2, revision=0x84, Arch Linux's intel-ucode Пакет просто предоставляет обновление, вам нужно отредактировать конфигурационные файлы, чтобы они действительно были загружены. Так что мое тестирование Skylake проводилось на i7-6700k с ревизией микрокода 0x84, которая не включает исправление для SKL150. Это соответствует поведению Хасвелла в каждом случае, который я проверял, IIRC. (например, и Haswell, и мой SKL могут запустить setne ah / add ah,ah / rcr ebx,1 / mov eax,ebx петля из лсд). У меня включен HT (что является предварительным условием для манифеста SKL150), но я тестировал в основном простаивающую систему, поэтому у моего потока было ядро.

С обновленным микрокодом LSD полностью отключен на все время, а не только когда активны частичные регистры. lsd.uops всегда точно равен нулю, в том числе для реальных программ, а не синтетических циклов. Аппаратные ошибки (а не ошибки микрокода) часто требуют отключения целой функции для исправления. Вот почему сообщается, что у SKL-avx512 (SKX) нет буфера обратной связи. К счастью, это не проблема с производительностью: повышенная пропускная способность SKL по сравнению с Broadwell почти всегда идет в ногу с проблемой / переименованием.


Дополнительная задержка AH / BH / CH / DH:

  • Чтение AH, когда оно не загрязнено (переименовано отдельно), добавляет дополнительный цикл задержки для обоих операндов. например add bl, ah имеет задержку 2 c от входа BL до выхода BL, поэтому он может добавить задержку к критическому пути, даже если RAX и AH не являются его частью. (Я видел такой вид дополнительной задержки для другого операнда ранее, с векторной задержкой на Skylake, где задержка int/float "загрязняет" регистр навсегда. TODO: запишите это.)

Это означает распаковку байтов с movzx ecx, al / movzx edx, ah имеет дополнительные задержки против movzx / shr eax,8 / movzx, но все же лучшая пропускная способность.

  • Чтение AH, когда оно грязное, не добавляет задержки. (add ah,ah или же add ah,dh / add dh,ah иметь задержку 1с на добавление). Я не провел много испытаний, чтобы подтвердить это во многих угловых случаях.

    Гипотеза: грязное значение high8 хранится в нижней части физического регистра. Чтение чистого старшего 8 требует сдвига для извлечения битов [15:8], но чтение грязного старшего 8 может просто взять биты [7:0] физического регистра как обычное чтение 8-битного регистра.

Дополнительная задержка не означает снижение пропускной способности. Эта программа может работать на 1 iter за 2 часа, хотя все add инструкции имеют задержку 2с (от чтения DH, который не изменяется)

global _start
_start:
    mov     ebp, 100000000
.loop:
    add ah, dh
    add bh, dh
    add ch, dh
    add al, dh
    add bl, dh
    add cl, dh
    add dl, dh

    dec ebp
    jnz .loop

    xor edi,edi
    mov eax,231   ; __NR_exit_group  from /usr/include/asm/unistd_64.h
    syscall       ; sys_exit_group(0)

 Performance counter stats for './testloop':

     48.943652      task-clock (msec)         #    0.997 CPUs utilized          
             1      context-switches          #    0.020 K/sec                  
             0      cpu-migrations            #    0.000 K/sec                  
             3      page-faults               #    0.061 K/sec                  
   200,314,806      cycles                    #    4.093 GHz                    
   100,024,930      branches                  # 2043.675 M/sec                  
   900,136,527      instructions              #    4.49  insn per cycle         
   800,219,617      uops_issued_any           # 16349.814 M/sec                 
   800,219,014      uops_executed_thread      # 16349.802 M/sec                 
         1,903      lsd_uops                  #    0.039 M/sec                  

   0.049107358 seconds time elapsed

Некоторые интересные тестовые циклы:

%if 1
     imul eax,eax
     mov  dh, al
     inc dh
     inc dh
     inc dh
;     add al, dl
    mov cl,dl
    movzx eax,cl
%endif

Runs at ~2.35c per iteration on both HSW and SKL.  reading `dl` has no dep on the `inc dh` result.  But using `movzx eax, dl` instead of `mov cl,dl` / `movzx eax,cl` causes a partial-register merge, and creates a loop-carried dep chain.  (8c per iteration).


%if 1
    imul  eax, eax
    imul  eax, eax
    imul  eax, eax
    imul  eax, eax
    imul  eax, eax         ; off the critical path unless there's a false dep

  %if 1
    test  ebx, ebx          ; independent of the imul results
    ;mov   ah, 123         ; dependent on RAX
    ;mov  eax,0           ; breaks the RAX dependency
    setz  ah              ; dependent on RAX
  %else
    mov   ah, bl          ; dep-breaking
  %endif

    add   ah, ah
    ;; ;inc   eax
;    sbb   eax,eax

    rcr   ebx, 1      ; dep on  add ah,ah  via CF
    mov   eax,ebx     ; clear AH-dirty

    ;; mov   [rdi], ah
    ;; movzx eax, byte [rdi]   ; clear AH-dirty, and remove dep on old value of RAX
    ;; add   ebx, eax          ; make the dep chain through AH loop-carried
%endif

Версия setcc (с %if 1) имеет задержку 20c, переносимую циклом, и запускается из LSD, хотя и имеет setcc ah а также add ah,ah,

00000000004000e0 <_start.loop>:
  4000e0:       0f af c0                imul   eax,eax
  4000e3:       0f af c0                imul   eax,eax
  4000e6:       0f af c0                imul   eax,eax
  4000e9:       0f af c0                imul   eax,eax
  4000ec:       0f af c0                imul   eax,eax
  4000ef:       85 db                   test   ebx,ebx
  4000f1:       0f 94 d4                sete   ah
  4000f4:       00 e4                   add    ah,ah
  4000f6:       d1 db                   rcr    ebx,1
  4000f8:       89 d8                   mov    eax,ebx
  4000fa:       ff cd                   dec    ebp
  4000fc:       75 e2                   jne    4000e0 <_start.loop>

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

       4565.851575      task-clock (msec)         #    1.000 CPUs utilized            ( +-  0.08% )
                 4      context-switches          #    0.001 K/sec                    ( +-  5.88% )
                 0      cpu-migrations            #    0.000 K/sec                  
                 3      page-faults               #    0.001 K/sec                  
    20,007,739,240      cycles                    #    4.382 GHz                      ( +-  0.00% )
     1,001,181,788      branches                  #  219.276 M/sec                    ( +-  0.00% )
    12,006,455,028      instructions              #    0.60  insn per cycle           ( +-  0.00% )
    13,009,415,501      uops_issued_any           # 2849.286 M/sec                    ( +-  0.00% )
    12,009,592,328      uops_executed_thread      # 2630.307 M/sec                    ( +-  0.00% )
    13,055,852,774      lsd_uops                  # 2859.456 M/sec                    ( +-  0.29% )

       4.565914158 seconds time elapsed                                          ( +-  0.08% )

Необъяснимый: он запускается из ЛСД, хотя и делает АХ грязным. (По крайней мере, я думаю, что это так. TODO: попробуйте добавить некоторые инструкции, которые делают что-то с eax перед mov eax,ebx очищает это.)

Но с mov ah, bl, он работает в 5.0c за итерацию (imul узкое место пропускной способности) на обоих HSW/SKL. (Закомментированное сохранение / перезагрузка тоже работает, но SKL имеет более быструю пересылку хранилищ, чем HSW, и с переменной задержкой...)

 #  mov ah, bl   version
 5,009,785,393      cycles                    #    4.289 GHz                      ( +-  0.08% )
 1,000,315,930      branches                  #  856.373 M/sec                    ( +-  0.00% )
11,001,728,338      instructions              #    2.20  insn per cycle           ( +-  0.00% )
12,003,003,708      uops_issued_any           # 10275.807 M/sec                   ( +-  0.00% )
11,002,974,066      uops_executed_thread      # 9419.678 M/sec                    ( +-  0.00% )
         1,806      lsd_uops                  #    0.002 M/sec                    ( +-  3.88% )

   1.168238322 seconds time elapsed                                          ( +-  0.33% )

Обратите внимание, что он больше не запускается из ЛСД.

Обновление: возможное свидетельство того, что IvyBridge по-прежнему переименовывает регистры low16 / low8 отдельно от полного регистра, как Sandybridge, но в отличие от Haswell и более поздних.

Результаты InstLatX64 от SnB и IvB показывают пропускную способность 0.33c для movsx r16, r8 (как и ожидалось, movsx никогда не устраняется, и до Haswell было только 3 ALU).

Но, видимо, InstLat's movsx r16, r8 тестировать узкие места Haswell / Broadwell / Skylake с пропускной способностью 1с (см. также этот отчет об ошибках на instlat github). Возможно, написав тот же архитектурный регистр, создав цепочку слияний.

(Фактическая пропускная способность для этой инструкции с отдельными регистрами назначения составляет 0,25 с на моем Skylake. Протестировано с 7 movsx написание инструкций в eax..edi и r10w/r11w, все чтение из cl, И dec ebp/jnz как ветвь петли, чтобы сделать цикл по 8 моп.)

Если я правильно догадываюсь о том, что создало тот результат пропускной способности 1с на процессорах после IvB, это делает что-то вроде запуска блока movsx dx, al, И это может работать только на более чем 1 IPC на процессорах, которые переименовывают dx отдельно от RDX вместо слияния. Таким образом, мы можем сделать вывод, что IvB на самом деле все же переименовывает регистры low8 / low16 отдельно от полных регистров, и только в Haswell они их отбросили. (Но здесь что-то подозрительно: если это объяснение было правильным, мы должны увидеть ту же пропускную способность 1с на AMD, которая не переименовывает частичные регистры. Но мы не видим, см. Ниже.)

Результаты с пропускной способностью ~0.33c для movsx r16, r8 (а также movzx r16, r8) тесты:

Haswell результаты с загадочным 0.58c пропускная способность для movsx/zx r16, r8:

Другие ранние и более поздние результаты Haswell (и CrystalWell) / Broadwell / Skylake имеют пропускную способность 1.0c для этих двух тестов.

  • HSW с 4.1.570.0 5 июня 2013 года, BDW с 4.3.15787.0 12 октября 2018 года, BDW с 4.3.739.0 17 марта 2017 года.

Как я сообщал в связанном выпуске InstLat на github, "задержки" для movzx r32, r8 игнорировать mov-elission, предположительно, проверяя как movzx eax, al,

Еще хуже то, что более новые версии InstLatX64 с отдельными регистрами версий теста, такие как MOVSX r1_32, r2_8, покажите числа задержки ниже 1 цикла, как 0.3c для этого MOV SX на Skylake. Это полная чушь; Я проверил просто чтобы быть уверенным.

MOVSX r1_16, r2_8 test показывает задержку 1с, поэтому, по-видимому, они просто измеряют задержку выходной (ложной) зависимости. (Который не существует для 32-битных и более широких выходов).

Но это MOVSX r1_16, r2_8 Тест измерял задержку 1с на Sandybridge! Так что, возможно, моя теория была неверной о том, что movsx r16, r8 тест говорит нам.


На Ryzen (AIDA64 build 4.3.781.0 21 февраля 2018 г.), который, как мы знаем, вообще не выполняет частичное переименование регистров, результаты не показывают эффект пропускной способности 1c, который мы ожидаем, если бы тест действительно записывал один и тот же 16-битный регистр повторно. Я не нахожу его ни на каких более старых процессорах AMD, с более старыми версиями InstLatX64, такими как K10 или семейство Bulldozer.

  43 X86     :MOVSX r16, r8                L:   0.28ns=  1.0c  T:   0.11ns=  0.40c
  44 X86     :MOVSX r32, r8                L:   0.28ns=  1.0c  T:   0.07ns=  0.25c
  45 AMD64   :MOVSX r64, r8                L:   0.28ns=  1.0c  T:   0.12ns=  0.43c
  46 X86     :MOVSX r32, r16               L:   0.28ns=  1.0c  T:   0.12ns=  0.43c
  47 AMD64   :MOVSX r64, r16               L:   0.28ns=  1.0c  T:   0.13ns=  0.45c
  48 AMD64   :MOVSXD r64, r32              L:   0.28ns=  1.0c  T:   0.13ns=  0.45c

ИДК, почему пропускная способность не составляет 0,25 для всех из них; кажется странным Это может быть версия эффекта пропускной способности 0.58c Haswell. Числа MOVZX одинаковы, с пропускной способностью 0,25 для версии без префиксов, которая читает R8 и записывает R32. Может быть, есть узкое место при извлечении / декодировании для больших инструкций? Но movsx r32, r16 такой же размер как movsx r32, r8,

Тесты Отдельные-рег показывают ту же схему, что и на Intel, однако, с задержкой 1с только для того, который должен объединиться. MOVZX такой же.

2252 X86     :MOVSX r1_16, r2_8            L:   0.28ns=  1.0c  T:   0.08ns=  0.28c
2253 X86     :MOVSX r1_32, r2_8            L:   0.07ns=  0.3c  T:   0.07ns=  0.25c
2254 AMD64   :MOVSX r1_64, r2_8            L:   0.07ns=  0.3c  T:   0.07ns=  0.25c
2255 X86     :MOVSX r1_32, r2_16           L:   0.07ns=  0.3c  T:   0.07ns=  0.25c

Результаты экскаватора также очень похожи на это, но, конечно, более низкая пропускная способность.

Другие вопросы по тегам