Может ли ЛСД выдавать 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.ANYcmask=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. Но это не имеет значения, потому что здесь нет микрофузии.

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