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 соответственно. В следующем разделе я расскажу, что происходит для больших значений. Обратите внимание, что, когда знаменатель равен нулю, значение не может быть вычислено, и поэтому оно не отображается.
![cycle metrics](https://i.stack.imgur.com/m16Bn.png)
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](https://i.stack.imgur.com/F1Mk7.png)
Когда 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 в противном случае.