Может ли ЛСД выдавать UOP со следующей итерации обнаруженного цикла?
Я играл, исследуя возможности филиала на порту 0 моего Haswell, начиная с очень простого цикла:
BITS 64
GLOBAL _start
SECTION .text
_start:
mov ecx, 10000000
.loop:
dec ecx ;|
jz .end ;| 1 uOP (call it D)
jmp .loop ;| 1 uOP (call it J)
.end:
mov eax, 60
xor edi, edi
syscall
С помощью perf
мы видим, что цикл работает на 1c / iter
Performance counter stats for './main' (50 runs):
10,001,055 uops_executed_port_port_6 ( +- 0.00% )
9,999,973 uops_executed_port_port_0 ( +- 0.00% )
10,015,414 cycles:u ( +- 0.02% )
23 resource_stalls_rs ( +- 64.05% )
Мои интерпретации этих результатов:
- D и J отправляются параллельно.
- J имеет обратную пропускную способность 1 цикла.
- D и J отправляются оптимально.
Тем не менее, мы также видим, что RS никогда не наполняется.
Он может отправлять uOP с максимальной скоростью 2 uOP /c, но теоретически может получить 4 uOP /c, что приводит к полному RS примерно через 30 с (для RS с размером записей в 60 слитых доменов).
Насколько я понимаю, должно быть очень мало ошибочных прогнозов, и все uOP должны исходить от ЛСД.
Итак, я посмотрел на ИП:
8,239,091 lsd_cycles_active ( +- 3.10% )
989,320 idq_dsb_cycles ( +- 23.47% )
2,534,972 idq_mite_cycles ( +- 15.43% )
4,929 idq_ms_uops ( +- 8.30% )
0.007429733 seconds time elapsed ( +- 1.79% )
который подтверждает, что FE выдает из LSD1.
Тем не менее, LSD никогда не выдает 4 uOPs/c:
7,591,866 lsd_cycles_active ( +- 3.17% )
0 lsd_cycles_4_uops
Моя интерпретация заключается в том, что LSD не может выдавать uOP от следующей итерации2, тем самым отправляя только пары D J в BE каждый цикл.
Правильна ли моя интерпретация?
Исходный код находится в этом хранилище.
1 Существует небольшая разница, я думаю, это связано с большим количеством итераций, которые позволяют переключать контексты.
2 Звучит довольно сложно в аппаратных средствах с ограниченной глубиной микросхемы.
1 ответ
Все мопы в вашем цикле являются ветвями (2 на итерацию). Я думаю, что причина того, что `lsd_cycles_4_uops равен нулю, заключается в ограничении переименователя. Согласно разделу 2.4.3.1 Руководства по оптимизации Intel:
Переименователь может выделять две ветви в каждом цикле по сравнению с одной веткой в каждом цикле в предыдущей микроархитектуре. Это может устранить некоторые пузыри в исполнении.
Это подраздел раздела микроархитектуры "Песчаный мост". Но, насколько мне известно, это относится ко всем более поздним микроархитектурам. Максимальная пропускная способность переименования составляет 4 моп за цикл. Но не более двух мопов могут быть ветвями. Таким образом, в этом примере, где все мопы являются ветвями, LSD никогда не сможет доставить более 2 мопов в любой данный цикл даже на первой итерации цикла.
Таким образом, 2 RSU будут распределены в RS за цикл, и оба (один предположительно принят и один не взят) могут быть отправлены за цикл. Так что заполняемость РС не растет.
Это ограничение не влияет на производительность вашей программы. Выполнение 2-х переходов мопов за цикл, давая IPC 3 за цикл, уже оптимально.
Я попытался найти событие производительности, которое может захватить задержки распределителя из-за этого ограничения. События RESOURCE_STALLS.ANY
а также UOPS_ISSUED.ANY
(с cmask
=1 и inv
=1) в данном случае не похоже @IwillnotexistIdonotexist предложил использовать IDQ_UOPS_NOT_DELIVERED.CORE
, Я представляю результаты ниже для события производительности и всех его поддерживаемых вариантов. Я также даю правильное значение этих событий, потому что руководство неверно. T
обозначает количество итераций.
IDQ_UOPS_NOT_DELIVERED.CORE
: Подсчитывает количество слотов, которые не были использованы распределителем. Если программа выполнялась для циклов ядра C, то общее количество слотов равно 4*C. Измеренное значение практически равно 2* Т. Поскольку количество циклов равно T, количество временных интервалов равно 4*T, что означает, что около половины временных интервалов выдачи не было использовано.
IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE
: Подсчитывает количество циклов, когда ноль мопов было доставлено из IDQ. Измеренное значение незначительно.
IDQ_UOPS_NOT_DELIVERED.CYCLES_LE_1_UOP_DELIV.CORE
: Подсчитывает количество циклов, в течение которых с IDQ было доставлено не более 1 моп. Измеренное значение незначительно.
IDQ_UOPS_NOT_DELIVERED.CYCLES_LE_2_UOP_DELIV.CORE
: Подсчитывает количество циклов, в течение которых с IDQ было доставлено не более 2 моп: Измеренное значение почти равно T.
IDQ_UOPS_NOT_DELIVERED.CYCLES_LE_3_UOP_DELIV.CORE
: Подсчитывает количество циклов, в течение которых с IDQ было доставлено не более 3 моп: Измеренное значение почти равно T.
Поэтому, поскольку время выполнения почти равно T базовых циклов, мы можем сделать вывод, что распределитель выделяет только 2 мопа за цикл в большинстве циклов, что равно частоте диспетчеризации.
Обратите внимание, что RS в Haswell и Skylake содержит неиспользованные мопы. Таким образом, каждая запись может содержать один неиспользованный моп. Смотри сноску 2. Но это не имеет значения, потому что здесь нет микрофузии.