Почему jnz требуется 2 цикла для завершения во внутреннем цикле - PullRequest
0 голосов
/ 12 января 2019

Я на IvyBridge. Я обнаружил несоответствие производительности jnz во внутреннем и внешнем циклах.

Следующая простая программа имеет внутренний цикл с фиксированным размером 16:

global _start
_start:
    mov rcx, 100000000
.loop_outer:
    mov rax,    16

.loop_inner:
    dec rax
    jnz .loop_inner

    dec rcx
    jnz .loop_outer

    xor edi, edi
    mov eax, 60
    syscall

perf инструмент показывает, что внешний цикл работает 32c / iter. Это предполагает, что для jnz требуется 2 цикла.

Затем я выполняю поиск в таблице инструкций Агнера, условный переход имеет 1-2 «взаимной пропускной способности» с комментарием «быстро, если нет перехода».

В этот момент я начинаю верить, что вышеописанное поведение каким-то образом ожидается. Но почему jnz во внешнем цикле требуется только 1 цикл для завершения?

Если я полностью удаляю часть .loop_inner, внешний цикл запускается 1c / iter. Поведение выглядит противоречивым.

Что мне здесь не хватает?

Редактировать для получения дополнительной информации:

Результат perf для вышеуказанной программы с командой:

perf stat -ecycles,branches,branch-misses,lsd.uops,uops_issued.any -r4 ./a.out

есть:

 3,215,921,579      cycles                                                        ( +-  0.11% )  (79.83%)
 1,701,361,270      branches                                                      ( +-  0.02% )  (80.05%)
        19,212      branch-misses             #    0.00% of all branches          ( +- 17.72% )  (80.09%)
        31,052      lsd.uops                                                      ( +- 76.58% )  (80.09%)
 1,803,009,428      uops_issued.any                                               ( +-  0.08% )  (79.93%)

Результат perf контрольного случая:

global _start
_start:
    mov rcx, 100000000
.loop_outer:
    mov rax,    16
    dec rcx
    jnz .loop_outer

    xor edi, edi
    mov eax, 60
    syscall

есть:

   100,978,250      cycles                                                        ( +-  0.66% )  (75.75%)
   100,606,742      branches                                                      ( +-  0.59% )  (75.74%)
         1,825      branch-misses             #    0.00% of all branches          ( +- 13.15% )  (81.22%)
   199,698,873      lsd.uops                                                      ( +-  0.07% )  (87.87%)
   200,300,606      uops_issued.any                                               ( +-  0.12% )  (79.42%)

Так что причина в основном ясна: ЛСД перестает работать по какой-то причине во вложенном случае. Уменьшение размера внутреннего цикла немного уменьшит медлительность, но не полностью.

В поисках Intel «руководства по оптимизации» я обнаружил, что LSD не будет работать, если цикл содержит «более восьми взятых веток». Это как-то объясняет поведение.

Ответы [ 2 ]

0 голосов
/ 19 января 2019

(Частичный ответ / предположение Я не закончил писать до того, как Хади опубликовал подробный анализ; часть этого продолжается из комментариев)

Утверждение Агнера "буфер цикла не имеет измеримого эффекта в тех случаях, когда кэш-память uop не является узким местом ..." неверно? Потому что это, безусловно, измеримый эффект, и кэш-память UOP не является узким местом, поскольку кэш-память имеет емкость ~ 1,5 КБ.

Да, Агнер называет это буфером обратной связи. Его утверждение состоит в том, что добавление ЛСД в дизайн не ускоряет код . Но да, это кажется неправильным для очень узких циклов, по крайней мере для вложенных циклов. Очевидно SnB / IvB действительно нуждается в буфере цикла, чтобы выпустить или выполнить циклы 1c / iter. Если микроархитектурное узкое место заключается в получении мопов из кеша мопов после ветвления, и в этом случае его предостережение покрывает это.

Существуют и другие случаи, кроме пропусков uop-кеша, когда чтение кеша uop может быть узким местом . например если мопы не были упакованы очень хорошо из-за эффектов выравнивания, или если они используют большие непосредственные значения и / или смещения, которые требуют дополнительных циклов для чтения из кеша мопов. См. Раздел «Песчаный мост» в Руководстве Агар Фога по изучению для более подробной информации об этих эффектах. Ваше предположение, что пропускная способность (до 1,5 тыс. Моп, если они идеально упакованы) является единственной причиной, по которой она может быть медленной, очень ошибочно.

Кстати, обновление микрокода для Skylake полностью отключило LSD, чтобы исправить ошибку слияния с частичным регистром, ошибка SKL150 1 , и это на самом деле не имело никакого эффекта, кроме случаев, когда крошечный цикл занимает 32B границу и требует 2 строки кэша.

Но Агнер перечисляет JMP rel8/32 и воспринимает пропускную способность JCC как 1-2 цикла в HSW / SKL против 2 в IvB. Так что что-то в взятых ветвях могло ускориться после IvB, кроме самого ЛСД.

Может существовать некоторая часть (и) ЦП, кроме LSD, которая также имеет особый случай для длительных крошечных циклов, которые позволяют им запускать 1 сделанный прыжок за такт, на Haswell и позже. Я не проверял, какие условия вызывают пропускную способность ветвления 1 на 2 цикла в HSW / SKL. Также обратите внимание, что Агнер измерял перед обновлением микрокода для ошибки SKL150.


сноска 1 : См. Как именно работают частичные регистры на Haswell / Skylake? Написание AL, похоже, ложно зависит от RAX, а AH несовместимо , и обратите внимание, что SKX и Kaby Lake поставляются с микрокодом, который уже включает это. Наконец, он снова включен в Coffee Lake, что исправило ошибочную аппаратную логику, так что LSD можно безопасно включить снова. https://en.wikichip.org/wiki/intel/microarchitectures/coffee_lake#Key_changes_from_Kaby_Lake

0 голосов
/ 19 января 2019

TL; DR : DSB, по-видимому, способен доставлять только один скачок внутреннего цикла каждый второй цикл. Также переключатели DSB-MITE составляют до 9% времени выполнения.


Введение. Часть 1. Понимание событий производительности LSD

Сначала я расскажу о событиях производительности LSD.UOPS и LSD.CYCLES_ACTIVE и некоторых особенностях LSD на микроархитектурах IvB и SnB. Как только мы установим это основание, мы сможем ответить на вопрос. Для этого мы можем использовать небольшие кусочки кода, которые специально предназначены для точного определения, когда эти события происходят.

Согласно документации:

LSD.UOPS: количество мопов, доставленных ЛСД.
LSD.CYCLES_ACTIVE: Циклы Uops доставлены ЛСД, но не пришли от декодера.

Эти определения полезны, но, как вы увидите позже, недостаточно точны, чтобы ответить на ваш вопрос. Важно лучше понять эти события. Некоторая информация, представленная здесь, не документирована Intel, и это только моя лучшая интерпретация эмпирических результатов и некоторых связанных с ними патентов, которые я получил. Хотя мне не удалось найти конкретный патент, описывающий реализацию LSD в микроархитектурах SnB или более поздних.

Каждый из следующих эталонных тестов начинается с комментария, который содержит название эталонного теста. Все числа нормализуются за итерацию, если не указано иное.

; B1
----------------------------------------------------
    mov rax, 100000000
.loop:
    dec rax
    jnz .loop
----------------------------------------------------
Metric                             |  IvB   |  SnB
----------------------------------------------------
cycles                             |  0.90  |  1.00
LSD.UOPS                           |  0.99  |  1.99
LSD.CYCLES_ACTIVE                  |  0.49  |  0.99
CYCLE_ACTIVITY.CYCLES_NO_EXECUTE   |  0.00  |  0.00
UOPS_ISSUED.STALL_CYCLES           |  0.43  |  0.50

Обе инструкции в теле цикла объединены в одну строку. На IvB и SnB есть только один порт выполнения, который может выполнять команды перехода. Следовательно, максимальная пропускная способность должна составлять 1 с / iter. Тем не менее, по какой-то причине IvB работает на 10% быстрее.

Согласно Снижается ли производительность при выполнении циклов, чей счетчик числа операций не кратен ширине процессора? , LSD в IvB и SnB не может выдавать значения uops через границы тела цикла, даже если есть доступные слоты выдачи , Поскольку цикл содержит один моп, мы ожидаем, что LSD будет выдавать один моп за цикл и что LSD.CYCLES_ACTIVE должно быть примерно равно общему числу циклов.

На IvB LSD.UOPS соответствует ожидаемому. То есть ЛСД будет выдавать один моп за цикл. Обратите внимание, что, поскольку число циклов равно количеству итераций, которое равно количеству мопов, мы можем эквивалентно сказать, что LSD выдает один моп на одну итерацию. По сути, большинство выполненных мопов было выпущено из ЛСД. Однако LSD.CYCLES_ACTIVE составляет около половины числа циклов. Как это возможно? В этом случае не должна ли быть выдана только половина от общего числа мопов из ЛСД? Я думаю, что здесь происходит то, что цикл по существу разворачивается дважды, и за цикл выдается два мопа. Тем не менее, только один моп может быть выполнен за цикл, но RESOURCE_STALLS.RS равен нулю, указывая на то, что RS никогда не заполняется. Тем не менее, RESOURCE_STALLS.ANY составляет около половины числа циклов. Собирая все это вместе сейчас, кажется, что ЛСД фактически выдает 2 мопа каждый второй цикл и что существует некоторое структурное ограничение, которое достигается через каждый второй цикл. CYCLE_ACTIVITY.CYCLES_NO_EXECUTE подтверждает, что в RS всегда есть хотя бы один read uop в любой заданный цикл. Следующие эксперименты покажут условия для развертывания.

На SnB, LSD.UOPS показывает, что от LSD было получено вдвое больше общего количества мопов. Также LSD.CYCLES_ACTIVE указывает, что ЛСД был активен большую часть времени. CYCLE_ACTIVITY.CYCLES_NO_EXECUTE и UOPS_ISSUED.STALL_CYCLES такие же, как на IvB. Следующие эксперименты помогают понять, что происходит. Кажется, что измеренное LSD.CYCLES_ACTIVE равно действительному LSD.CYCLES_ACTIVE + RESOURCE_STALLS.ANY. Поэтому для получения действительного LSD.CYCLES_ACTIVE необходимо вычесть RESOURCE_STALLS.ANY из измеренного LSD.CYCLES_ACTIVE. То же относится и к LSD.CYCLES_4_UOPS. Действительное значение LSD.UOPS можно рассчитать следующим образом:

LSD.UOPS измерено = LSD.UOPS реальное + ((LSD.UOPS измерено / LSD.CYCLES_ACTIVE измерено ) * RESOURCE_STALLS.ANY)

Таким образом,

LSD.UOPS реальное = LSD.UOPS измерено - ((LSD.UOPS измерено / LSD.CYCLES_ACTIVE измерено ) * RESOURCE_STALLS.ANY)
= LSD.UOPS измерено * (1 - (RESOURCE_STALLS.ANY / LSD.CYCLES_ACTIVE измерено ))

Для всех тестов, которые я выполнял на SnB (включая те, которые не показаны здесь), эти корректировки являются точными.

Обратите внимание, что RESOURCE_STALLS.RS и RESOURCE_STALLS.ANY на SnB похожи на IvB. Таким образом, кажется, что LSD работает на IvB и SnB точно так же, как этот конкретный тест, за исключением того, что события LSD.UOPS и LSD.CYCLES_ACTIVE подсчитываются по-разному.

; B2
----------------------------------------------------
    mov rax, 100000000
    mov rbx, 0
.loop:
    dec rbx
    jz .loop
    dec rax
    jnz .loop
----------------------------------------------------
Metric                             |  IvB   |  SnB
----------------------------------------------------
cycles                             |  1.98  |  2.00
LSD.UOPS                           |  1.92  |  3.99
LSD.CYCLES_ACTIVE                  |  0.94  |  1.99
CYCLE_ACTIVITY.CYCLES_NO_EXECUTE   |  0.00  |  0.00
UOPS_ISSUED.STALL_CYCLES           |  1.00  |  1.00

В В2 на каждую итерацию приходится 2 мопа, и оба являются прыжками. Первый никогда не берется, поэтому остается только один цикл. Мы ожидаем, что он будет работать на 2c / iter, что действительно так. LSD.UOPS показывает, что большинство мопов было выпущено из ЛСД, но LSD.CYCLES_ACTIVE показывает, что ЛСД был активен только половину времени. Это означает, что цикл не был развернут. Таким образом, кажется, что развертывание происходит только тогда, когда в цикле есть один моп.

; B3
----------------------------------------------------
    mov rax, 100000000
.loop:
    dec rbx
    dec rax
    jnz .loop
----------------------------------------------------
Metric                             |  IvB   |  SnB
----------------------------------------------------
cycles                             |  0.90  |  1.00
LSD.UOPS                           |  1.99  |  1.99
LSD.CYCLES_ACTIVE                  |  0.99  |  0.99
CYCLE_ACTIVITY.CYCLES_NO_EXECUTE   |  0.00  |  0.00
UOPS_ISSUED.STALL_CYCLES           |  0.00  |  0.00

Здесь также есть 2 мопа, но первый - это мера ALU с одним циклом, которая не связана с моп-прыжком. B3 помогает нам ответить на следующие два вопроса:

  • Если целью прыжка не является прыжковый прыжок, будут ли LSD.UOPS и LSD.CYCLES_ACTIVE по-прежнему рассчитываться дважды на SnB?
  • Если цикл содержит 2 мопа, где только один из них является прыжком, развернет ли ЛСД цикл?

B3 показывает, что ответом на оба вопроса является "Нет".

UOPS_ISSUED.STALL_CYCLES предполагает, что LSD остановит только один цикл, если он выдаст два прыжковых мопа за один цикл. Этого никогда не происходит в B3, поэтому здесь нет киосков.

; B4
----------------------------------------------------
    mov rax, 100000000
.loop:
    add rbx, qword [buf]
    dec rax
    jnz .loop
----------------------------------------------------
Metric                             |  IvB   |  SnB
----------------------------------------------------
cycles                             |  0.90  |  1.00
LSD.UOPS                           |  1.99  |  2.00
LSD.CYCLES_ACTIVE                  |  0.99  |  1.00
CYCLE_ACTIVITY.CYCLES_NO_EXECUTE   |  0.00  |  0.00
UOPS_ISSUED.STALL_CYCLES           |  0.00  |  0.00

B4 имеет дополнительный поворот к нему; он содержит 2 мопа в объединенном домене, но 3 мопа в объединенном домене, потому что инструкция load-ALU становится непригодной для RS. В предыдущих бенчмарках не было микросопливных мопов, только макроплавленные мопы. Цель здесь - увидеть, как ЛСД обрабатывает микроплавленые мопы.

LSD.UOPS показывает, что два мопа команды load-ALU заняли один слот выпуска (слитый прыжковый моп потребляет только один слот). Также, поскольку LSD.CYCLES_ACTIVE равно cycles, развертывание не произошло. Пропускная способность цикла соответствует ожидаемой.

; B5
----------------------------------------------------
    mov rax, 100000000
.loop:
    jmp .next
.next:
    dec rax
    jnz .loop
----------------------------------------------------
Metric                             |  IvB   |  SnB
----------------------------------------------------
cycles                             |  2.00  |  2.00
LSD.UOPS                           |  1.91  |  3.99
LSD.CYCLES_ACTIVE                  |  0.96  |  1.99
CYCLE_ACTIVITY.CYCLES_NO_EXECUTE   |  0.00  |  0.00
UOPS_ISSUED.STALL_CYCLES           |  1.00  |  1.00

B5 - последний тест, который нам понадобится. Это похоже на В2 в том, что оно содержит два ветвления. Тем не менее, один из прыжков в B5 - это безусловный прыжок вперед. Результаты идентичны B2, что указывает на то, что не имеет значения, является ли прыжковый переход условным или нет. Это также имеет место, если первый прыжок является условным, а второй - нет.

Введение - Часть 2. Прогнозирование ветвей в ЛСД

LSD - это механизм, реализованный в очереди uop (IDQ), который может улучшить производительность и снизить энергопотребление (следовательно, уменьшается тепловыделение). Это может повысить производительность, поскольку некоторые ограничения, существующие в интерфейсе, могут быть ослаблены в очереди uop. В частности, на SnB и IvB максимальная пропускная способность как путей MITE, так и DSB составляет 4 моп / с. , но с точки зрения байтов, это 16B / c и 32B / c, соответственно. Пропускная способность очереди uop также равна 4uops / c, но не имеет ограничений по количеству байтов. Пока LSD выдает мопы из очереди мопов, внешний интерфейс (т. Е. Блоки выборки и декодирования) и даже ненужная логика ниже IDQ могут быть отключены. До Nehalem LSD был реализован в модуле IQ . Начиная с Haswell, LSD поддерживает петли , которые содержат мопы из MSROM . LSD в процессорах Skylake отключен, потому что, по-видимому, он глючит.

Петли обычно содержат хотя бы одну условную ветвь. ЛСД по существу отслеживает обратные условные переходы и пытается определить последовательность мопов, составляющих цикл. Если LSD требуется слишком много времени для обнаружения петли, производительность может ухудшиться, а мощность может быть потеряна. С другой стороны, если ЛСД преждевременно блокирует цикл и пытается воспроизвести его, условный переход цикла может фактически провалиться. Это может быть обнаружено только после выполнения условного перехода, что означает, что более поздние мопы, возможно, уже были отправлены и отправлены для выполнения. Все эти мопы должны быть сброшены, а внешний интерфейс должен быть активирован для получения мопов с правильного пути. Таким образом, может быть значительное снижение производительности, если улучшение производительности от использования LSD не превышает снижение производительности в результате возможного неверного прогнозирования последнего выполнения условной ветви, где завершается цикл.

Мы уже знаем, что единица предсказания ветвления (BPU) в SnB и более поздних версиях может правильно предсказать, когда условная ветвь цикла проваливается, когда общее число итераций не превышает некоторого небольшого числа, после чего BPU предполагает, что цикл будет повторяться вечно. Если LSD использует сложные возможности BPU для прогнозирования прекращения блокировки, он должен иметь возможность правильно прогнозировать те же случаи. Возможно также, что LSD использует свой собственный предиктор ветвления, который потенциально намного проще. Давайте узнаем.

mov rcx, 100000000/(IC+3)
.loop_outer:
    mov rax, IC
    mov rbx, 1 

.loop_inner:
    dec rax
    jnz .loop_inner

    dec rcx
    jnz .loop_outer

Пусть OC и IC обозначают количество внешних итераций и количество внутренних итераций соответственно. Они связаны следующим образом:

OC = 100000000 / (IC + 3) где IC> 0

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

По сравнению с кодом из вопроса есть дополнительная инструкция mov rbx, 1, так что общее количество мопов во внешнем цикле составляет ровно 4 мопа. Это позволяет нам использовать событие производительности LSD.CYCLES_4_UOPS в дополнение к LSD.CYCLES_ACTIVE и BR_MISP_RETIRED.CONDITIONAL. Обратите внимание, что поскольку существует только один порт выполнения ветви, каждая итерация внешнего цикла занимает не менее 2 циклов (или, согласно таблице Агнера, 1-2 цикла). См. Также: Может ли ЛСД выдавать UOP со следующей итерации обнаруженного цикла? .

Общее количество прыжков:

OC + IC*OC = 100M / (IC + 3) + IC* 100M / (IC + 3)
= 100M (IC + 1) / (IC + 3)

Предполагая, что максимальная пропускная способность скачкообразного перехода равна 1 на цикл, оптимальное время выполнения составляет 100 М (IC + 1) / (IC + 3) циклов. На IvB мы можем вместо этого использовать максимальную пропускную способность при прыжке 0,9 / c, если мы хотим быть строгими. Было бы полезно разделить это на количество внутренних итераций:

OPT = (100M (IC + 1) / (IC + 3)) / (100M IC / (IC + 3)) =
100M (IC + 1) * (IC + 3) / (IC + 3) * 100M IC =
(IC + 1) / IC = 1 + 1 / IC

Следовательно, 1 <<code>OPT <= 1,5 для <code>IC> 1. Человек, разрабатывающий ЛСД, может использовать это для сравнения различных конструкций ЛСД. Мы также будем использовать это в ближайшее время. Другими словами, оптимальная производительность достигается, когда общее число циклов, деленное на общее количество переходов, равно 1 (или 0,9 на IvB).

Если предположить, что прогноз для двух скачков является независимым, и учитывая, что jnz .loop_outer легко предсказуемо, производительность зависит от прогноза jnz .loop_inner. При неправильном предсказании, которое меняет управление на uop вне заблокированного цикла, LSD завершает цикл и пытается обнаружить другой цикл. ЛСД может быть представлен как конечный автомат с тремя состояниями. В одном состоянии ЛСД ищет циклическое поведение. Во втором состоянии ЛСД изучает границы и количество итераций цикла. В третьем состоянии ЛСД воспроизводит цикл. Когда цикл существует, состояние изменяется с третьего на первое.

Как мы узнали из предыдущего набора экспериментов, на SnB будут дополнительные события ЛСД, когда возникнут связанные с бэкэндом киоски с проблемами. Таким образом, цифры должны быть поняты соответственно. Обратите внимание, что случай, когда IC = 1, не был проверен в предыдущем разделе. Это будет обсуждаться здесь. Напомним также, что как на IvB, так и на SnB внутренний цикл может быть развернут. Внешний цикл никогда не будет развернут, потому что он содержит более одного мопа. Кстати, LSD.CYCLES_4_UOPS работает как положено (извините, никаких сюрпризов).

На следующих рисунках показаны необработанные результаты. Я показал результаты только до IC = 13 и IC = 9 на IvB и SnB соответственно. В следующем разделе я расскажу, что происходит для больших значений. Обратите внимание, что, когда знаменатель равен нулю, значение не может быть вычислено, и поэтому оно не отображается.

uop metrics cycle metrics

LSD.UOPS/100M - это отношение количества мопов, выданных ЛСД, к общему количеству мопов. LSD.UOPS/OC - это среднее количество мопов, выданных LSD за внешнюю итерацию. LSD.UOPS/(OC*IC) - это среднее количество мопов, выданных LSD за внутреннюю итерацию. BR_MISP_RETIRED.CONDITIONAL/OC - это среднее число отклоненных условных ветвей, которые были ошибочно предсказаны за внешнюю итерацию, которое явно равно нулю как для IvB, так и для SnB для всех IC.

Для IC = 1 на IvB все мопы были выпущены из ЛСД. Внутренняя условная ветвь всегда не берется. Показатель LSD.CYCLES_4_UOPS/LSD.CYCLES_ACTIVE, показанный на втором рисунке, показывает, что во всех циклах, в которых активен LSD, LSD выдает 4 мопа за цикл. Из предыдущих экспериментов мы узнали, что когда LSD выдает 2 прыжковых мопа в одном цикле, он не может выдавать прыжковые мопы в следующем цикле из-за некоторых структурных ограничений, поэтому он остановится. LSD.CYCLES_ACTIVE/cycles показывает, что ЛСД останавливается (почти) каждый второй цикл. Мы ожидаем, что для выполнения внешней итерации требуется около 2 циклов, но cycles показывает, что для этого требуется около 1,8 цикла. Вероятно, это связано с пропускной способностью 0,9 на Uv, которую мы видели ранее.

Случай IC = 1 на SnB похож, за исключением двух вещей. Во-первых, внешний цикл на самом деле занимает 2 цикла, а не 1.8. Во-вторых, все три количества ЛСД событий вдвое превышают ожидаемые. Они могут быть скорректированы, как обсуждалось в предыдущем разделе.

Прогноз ветвления особенно интересен, когда IC> 1. Давайте проанализируем случай IC = 2 подробно. LSD.CYCLES_ACTIVE и LSD.CYCLES_4_UOPS показывают, что примерно в 32% всех циклов LSD активен, а в 50% этих циклов LSD выдает 4 мопа за цикл. Таким образом, существуют либо неправильные прогнозы, либо то, что LSD занимает много времени в состоянии обнаружения цикла или в состоянии обучения. Тем не менее, cycles / (OC*IC) составляет около 1,6, или, другими словами, cycles / jumps составляет 1,07, что близко к оптимальной производительности. Трудно понять, какие мопы выпускаются в группах по 4 из ЛСД и какие мопы выпускаются в группах размером менее 4 из ЛСД. На самом деле, мы не знаем, как ЛСД события учитываются при наличии ложных прогнозов. Потенциальное развертывание добавляет еще один уровень сложности. Количество событий LSD можно рассматривать как верхние границы полезных мопов, выпущенных ЛСД, и циклов, в которых ЛСД выдавал полезные мопы.

По мере увеличения IC, LSD.CYCLES_ACTIVE и LSD.CYCLES_4_UOPS уменьшаются, а производительность ухудшается медленно, но последовательно (помните, что cycles / (OC*IC) следует сравнивать с OPT). Это как если бы последняя итерация внутреннего цикла была неправильно предсказана, но ее штраф за неправильное предсказание увеличивается с IC. Обратите внимание, что BPU всегда правильно предсказывает количество итераций внутреннего цикла.


Ответ

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

mov rcx, 100000000/(IC+2)
.loop_outer:
    mov rax, IC

.loop_inner:
    dec rax
    jnz .loop_inner

    dec rcx
    jnz .loop_outer

По сути, это то же самое, что и код из вопроса. Единственное отличие состоит в том, что количество внешних итераций настраивается так, чтобы поддерживать одинаковое количество динамических мопов. Обратите внимание, что LSD.CYCLES_4_UOPS в этом случае бесполезен, потому что у LSD никогда не будет 4 мопов для выдачи в любом цикле. Все приведенные ниже цифры предназначены только для IvB. Не беспокойтесь о том, как отличается SnB, будет упомянуто в тексте.

enter image description here

Когда IC = 1, cycles / jumps составляет 0,7 (1,0 на SnB), что даже ниже 0,9. Я не знаю, как достигается эта пропускная способность. Производительность снижается при больших значениях IC, что коррелирует с уменьшением активных циклов LSD. Когда IC = 13-27 (9-27 на SnB), из LSD выдается ноль мопов. Я думаю, что в этом диапазоне LSD считает, что влияние на производительность из-за неправильного прогнозирования последней внутренней итерации больше некоторого порога, он решает никогда не блокировать цикл и запоминает свое решение. Когда IC <13, ЛСД выглядит агрессивным и, возможно, считает цикл более предсказуемым. Для <code>IC> 27 количество активных циклов LSD медленно увеличивается, что коррелирует с постепенным улучшением производительности. Хотя это не показано на рисунке, поскольку IC выходит далеко за пределы 64, большая часть мопов будет приходиться на ЛСД, а cycles / скачки устанавливаются на 0,9.

Результаты для диапазона IC = 13-27 особенно полезны. Циклы задержки выдачи составляют примерно половину общего числа циклов и также равны циклам задержки отправки. Именно по этой причине внутренний цикл выполняется на 2.0c / iter; потому что прыжки мопов внутреннего цикла выдается / отправляется каждый второй цикл. Когда LSD не активен, мопы могут поступать из DSB, MITE или MSROM. Ассистенты микрокода не требуются для нашего цикла, поэтому, возможно, есть ограничение в DSB, MITE или в обоих. Мы можем дополнительно исследовать, чтобы определить, где ограничения используют события производительности внешнего интерфейса. Я сделал это, и результаты показывают, что около 80-90% всех мопов происходят из DSB. Сам DSB имеет много ограничений, и кажется, что цикл поражает их. Кажется, что DSB занимает 2 цикла, чтобы доставить прыжок, который нацеливается на себя. Кроме того, для полного диапазона IC задержки из-за переключения MITE-DSB составляют до 9% всех циклов. Опять же, причина этих коммутаторов связана с ограничениями в самой DSB. Обратите внимание, что до 20% доставляется с пути MITE. Предполагая, что значения uops не превышают полосу пропускания 16B / c пути MITE, я думаю, что цикл был бы выполнен со скоростью 1c / iter, если бы не было DSB.

На приведенном выше рисунке также показана частота ошибочного прогнозирования BPU (за итерацию внешнего цикла). На IvB это ноль для IC = 1-33, за исключением случаев, когда IC = 21, 0-1, когда IC = 34-45, и ровно 1, когда IC> 46. На SnB это ноль для IC = 1-33 и 1 в противном случае.

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