Странные эффекты производительности от близлежащих зависимых хранилищ в цикле поиска указателя на IvyBridge. Добавление дополнительной нагрузки ускоряет его?

Сначала у меня есть приведенная ниже настройка на IvyBridge, я вставлю код измерения полезной нагрузки в прокомментированное место. Первые 8 байтов buf хранят адрес самого buf, я использую его для создания зависимостей с переносом цикла:

section .bss
align   64
buf:    resb    64

section .text
global _start
_start:
    mov rcx,         1000000000
    mov qword [buf], buf
    mov rax,         buf
loop:
    ; I will insert payload here
    ; as is described below 

    dec rcx
    jne loop

    xor rdi,    rdi
    mov rax,    60
    syscall

Дело 1:

Я вставляю в расположение полезной нагрузки:

mov qword [rax+8],  8
mov rax,            [rax]

perf показывает, что цикл составляет 5,4 цента / л. Это несколько понятно, потому что латентность L1d составляет 4 цикла.

случай 2:

Я меняю порядок этих двух инструкций:

mov rax,            [rax]
mov qword [rax+8],  8

Результат внезапно становится 9c / iter. Не понимаю почему. Поскольку первая инструкция следующей итерации не зависит от второй инструкции текущей итерации, этот параметр не должен отличаться от случая 1.

Я также использовал инструмент IACA для статического анализа этих двух случаев, но этот инструмент ненадежен, потому что он предсказывает один и тот же результат 5.71c / iter для обоих случаев, что противоречит эксперименту.

случай 3:

Затем я вставляю нерелевантную mov инструкцию к случаю 2:

mov rax,            [rax]
mov qword [rax+8],  8
mov rbx,            [rax+16] 

Теперь результат составляет 6,8 цента / л. Но как нерелевантная вставка mov может повысить скорость с 9c / iter до 6,8c / iter?

Инструмент IACA предсказывает неверный результат, так как в предыдущем случае он показывает 5,24 цента / итера.

Я сейчас совершенно запутался, как понять приведенные выше результаты?

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

В случае 1 и 2 есть адрес rax+8. Те же результаты остаются для случаев 1 и 2, если rax+8 изменяется на rax+16 или rax+24. Но что-то удивительное происходит, когда его меняют на rax+32: случай 1 становится 5,3c / iter, случай 2 внезапно становится 4,2c / iter.

Отредактируйте, чтобы увидеть больше perf событий:

$ perf stat -ecycles,ld_blocks_partial.address_alias,int_misc.recovery_cycles,machine_clears.count,uops_executed.stall_cycles,resource_stalls.any ./a.out

случай 1 для [rax+8]:

 5,429,070,287      cycles                                                        (66.53%)
         6,941      ld_blocks_partial.address_alias                                     (66.75%)
       426,528      int_misc.recovery_cycles                                      (66.83%)
        17,117      machine_clears.count                                          (66.84%)
 2,182,476,446      uops_executed.stall_cycles                                     (66.63%)
 4,386,210,668      resource_stalls.any                                           (66.41%)

случай 2 для [rax+8]:

 9,018,343,290      cycles                                                        (66.59%)
         8,266      ld_blocks_partial.address_alias                                     (66.73%)
       377,824      int_misc.recovery_cycles                                      (66.76%)
        10,159      machine_clears.count                                          (66.76%)
 7,010,861,225      uops_executed.stall_cycles                                     (66.65%)
 7,993,995,420      resource_stalls.any                                           (66.51%)

case 3 для [rax+8]:

 6,810,946,768      cycles                                                        (66.69%)
         1,641      ld_blocks_partial.address_alias                                     (66.73%)
       223,062      int_misc.recovery_cycles                                      (66.73%)
         7,349      machine_clears.count                                          (66.74%)
 3,618,236,557      uops_executed.stall_cycles                                     (66.58%)
 5,777,653,144      resource_stalls.any                                           (66.53%)

случай 2 для [rax+32]:

 4,202,233,246      cycles                                                        (66.68%)
         2,969      ld_blocks_partial.address_alias                                     (66.68%)
       149,308      int_misc.recovery_cycles                                      (66.68%)
         4,522      machine_clears.count                                          (66.68%)
 1,202,497,606      uops_executed.stall_cycles                                     (66.64%)
 3,179,044,737      resource_stalls.any                                           (66.64%)

person user10865622    schedule 08.01.2019    source источник
comment
Что касается случая 1, для выполнения записи в память требуется только 1 цикл (для операций STD и STA требуется по 1 циклу). Однако чтение занимает 4 цикла, поэтому 5.4c / iter имеет смысл. Что касается случая 3, третья инструкция mov приводит к конфликту между блоками AGU, поэтому производительность должна быть немного хуже, чем в случае 1, поэтому 6.8c / iter имеет смысл. Я не могу придумать очевидного объяснения случая 2, поэтому мы должны исследовать больше. Например, добавьте mov qword [rax+8], 8 непосредственно перед (вне) цикла и посмотрите, как это изменит производительность. Еще вы можете попробовать выровнять цикл по 32-байтовой границе.   -  person Hadi Brais    schedule 08.01.2019
comment
@HadiBrais Добавление mov перед циклом или выравнивание цикла не имеет значения. Но смещение 8 в [rax+8] может сыграть здесь важную роль, см. Мою правку.   -  person user10865622    schedule 09.01.2019
comment
Интересно. Поддерживает ли ваш процессор гиперпоточность? Убедитесь, что гиперпоточность отключена. Теперь нам нужно измерить следующие события производительности для каждого случая: LD_BLOCKS_PARTIAL.ADDRESS_ALIAS, INT_MISC.RECOVERY_CYCLES, MACHINE_CLEARS.COUNT, UOPS_EXECUTED.STALL_CYCLES, L1D_BLOCKS.BANK_CONFLICT_CYCLES и RESOURCE_STALLS.ANY. Конечно, нам также нужен счетчик необработанных циклов (который в perf называется cycles). Обратитесь к руководству по событиям производительности, чтобы определить коды событий для этих событий.   -  person Hadi Brais    schedule 09.01.2019
comment
В Skylake case1 = case2 = ~ 4,4 цикла на элемент, например, независимо от изменения смещения +8 на +256. Мы действительно знаем, что есть большие различия в том, как интерфейс будет передавать этот цикл ядру OoO на IvB и SKL, поэтому мне интересно, существенно ли это. Вы ожидаете, что не будет цикла, который работает с пропускной способностью менее ~ 1/4 от интерфейсной пропускной способности. В IvB он запускается из буфера цикла, и каждая итерация выполняется вместе, так что, вероятно, это связано с тем, почему изменение порядка может иметь значение.   -  person Peter Cordes    schedule 10.01.2019
comment
Конфликты банка кэша, вероятно, не являются фактором для +8, но могут быть для +128. Но +32 все еще находится в той же строке, записывая другой банк, и не пересекает линию или границу банка, потому что buf выровнен.   -  person Peter Cordes    schedule 10.01.2019
comment
Однако я могу воспроизвести ускорение с помощью case 3 на SKL. От 4,4 до 4,0 цикла на итерацию. (Но HSW и более поздние версии имеют хранилище AGU на порту 7, поэтому он никогда не конфликтует с нагрузками ...) Я не понимаю, почему любая из версий будет работать медленнее, чем 4,0 цикла на каждый (задержка при поиске указателя L1d). Конкретные числа задержки, которые вы видите, выглядят много как Есть ли штраф, если base + offset находится на другой странице, чем базовый? неоптимизированный (5c) и случай повтора (9c на HSW (и ранее?))), но я не знаю, почему это могло произойти.   -  person Peter Cordes    schedule 10.01.2019
comment
Изменение адреса магазина на [rel buf+8] приводит к тому, что случай 2 запускается на SKL со скоростью 4c на каждого пользователя. ЦП может запускать мопы адресов хранилища заранее, если интерфейсная часть вообще работает впереди, и быстрее обнаруживать, что загрузка и сохранение не перекрываются. Я думаю, что читал, что процессоры Intel используют динамическое предсказание для устранения неоднозначности памяти. (т.е. есть кеш-предиктор, который запоминает, что эта загрузка не нуждалась в переадресации хранилища.) Если это действительно так, единственный эффект от того, чтобы сделать адрес зависимым, состоит в том, что мопы с адресом хранилища не могут запускаться так рано. (Я также пробовал с abs buf, чтобы он мог плавиться: то же самое).   -  person Peter Cordes    schedule 10.01.2019
comment
@HadiBrais Отключить или включить гиперпоточность не имеет значения: \   -  person user10865622    schedule 10.01.2019
comment
@PeterCordes Я также тестировал [rel buf+8] на IvyBridge, все случаи становятся 4c / iter, так что это связано с зависимыми магазинами. Из любопытства, как часто на практике случаются подобные странности с микроархитектурой? Этот код выглядит настолько простым для более сложного реального кода, что мне интересно, действительно ли возможно предсказать количество циклов в критической части.   -  person user10865622    schedule 10.01.2019
comment
Несовершенное планирование или что-то еще иногда случается с чистыми циклами ALU, которые имеют взаимосвязанные зависимости, но обычно разветвление коротких независимых цепочек из цепочки, переносимой циклом, не снижает пропускной способности. Магазины и грузы усложняют задачу. Устранение неоднозначности памяти сложно, и x86 должен создавать иллюзию следования строгой семантике упорядочения памяти, в то же время выполняя агрессивно вне порядка, поэтому существует много mem HW. Прогнозирование пропускной способности в реальном коде, который перебирает один или два массива, обычно работает довольно точно, даже загрузка из одного и сохранение в другой.   -  person Peter Cordes    schedule 10.01.2019
comment
Было бы полезно, если бы вы могли измерить счетчики производительности, которые я упоминал в своем предыдущем комментарии, чтобы мы могли получить дополнительные подсказки для уверенного продолжения анализа. На данный момент у меня нет доступа к системе IvB, поэтому я не могу проводить эксперименты самостоятельно.   -  person Hadi Brais    schedule 10.01.2019
comment
@HadiBrais Добавлены некоторые результаты.   -  person user10865622    schedule 10.01.2019
comment
Мои результаты на Haswell отличаются от результатов IvB и SK, но также одинаково интересны. Case1 = case2 = 8.4c / iter и case 3 = 8.9c / iter. STALLS_LDM_PENDING равно CYCLES_NO_EXECUTE = 6c, что позволяет предположить, что эффективное влияние задержки загрузки на производительность составляет 6 наших из 8,4c, а задержка загрузки составляет не менее 6c. События UOPS_EXECUTED_PORT.PORT_X показывают, что _4 _ + _ 5 _ + _ 6_ = 5B, но ожидаемое значение равно 2B. Однако PORT_4, как и ожидалось, составляет 1B. По какой-то причине повторяется загрузка uop.   -  person Hadi Brais    schedule 10.01.2019
comment
Однако, если я изменю смещения [rax+8] на [rax+64] или больше, case1 и case2 станут 4.3c / iter и _3 _ + _ 4 _ + _ 5_ = 2B, как и ожидалось.   -  person Hadi Brais    schedule 10.01.2019
comment
@PeterCordes Я думаю, что на HSW загрузка и сохранение в одной строке кэша не могут выполняться параллельно. Если есть неперекрывающаяся загрузка и сохранение (которое должно быть зафиксировано) в одной и той же строке, блок памяти выберет один и выдаст его, а другой должен будет ждать, пока он завершится. Например, он может отдавать приоритет загрузкам над хранилищами, если буфер хранилища не заполнен или что-то в этом роде. Мои результаты показывают, что STALLS_LDM_PENDING мог уловить этот эффект. Результаты OP предполагают, что эта проблема может существовать на IvB, но с другим влиянием на производительность ...   -  person Hadi Brais    schedule 10.01.2019
comment
... Ваши результаты показывают, что этой проблемы не существует в SKL. AFAIK, в руководстве ничего не говорится о том, могут ли неперекрывающиеся загрузка и сохранение в одной строке выполняться параллельно для любой из микроархитектур. Он просто говорит о двух нагрузках на одну линию или банк. Мои результаты также показывают, что количество мопов, отправленных на порты 247, больше, чем ожидалось, что, вероятно, связано с тем, что планировщик считает, что загрузка займет 4с, но на самом деле это занимает больше времени, потому что оно блокируется магазином на той же строке, поэтому зависимые мопы должны быть повторно отправлены (воспроизведены).   -  person Hadi Brais    schedule 10.01.2019
comment
Я придумал другой метод анализа, чтобы прийти к такому же выводу, но он намного проще.   -  person Hadi Brais    schedule 23.01.2019
comment
@PeterCordes - да, для устранения неоднозначности в памяти используется предиктор. Подробную информацию о том, как это работает на Skylake, я помещаю здесь но я подозреваю, что это похоже и на более ранние арки.   -  person BeeOnRope    schedule 01.02.2019


Ответы (1)


Tl; DR: для этих трех случаев возникает штраф в несколько циклов при одновременном выполнении загрузки и сохранения. Задержка загрузки находится на критическом пути во всех трех случаях, но штраф в разных случаях различен. Случай 3 примерно на цикл больше, чем случай 1 из-за дополнительной нагрузки.


Метод анализа 1. Использование событий производительности останова

Мне удалось воспроизвести ваши результаты для всех трех случаев на IvB и SnB. Полученные мной числа находятся в пределах 2% от ваших. Количество циклов, необходимых для выполнения одной итерации для случаев 1, 2 и 4, составляет 5,4, 8,9 и 6,6 соответственно.

Начнем с внешнего интерфейса. События производительности LSD.CYCLESUOPS_ISSUED.STALL_CYCLESUOPS и LSD.CYCLESLSD.CYCLES_ACTIVEUOPS показывают, что в основном все мопы исходят от LSD. Кроме того, эти события вместе с LSD.CYCLES_ACTIVE показывают, что в каждом цикле, в котором LSD не остановлен, выдается 3 мопы в случаях 1 и 2 и 4 мопы в случае 3. Другими словами, как и ожидалось, мопы каждого итерации выдаются вместе в одной группе в едином цикле.

Во всех следующих соотношениях знак «= ~» означает, что разница находится в пределах 2%. Начну со следующего эмпирического наблюдения:

UOPS_ISSUED.STALL_CYCLES + LSD.CYCLES_ACTIVE =~ cycles

Обратите внимание, что количество событий LSD на SnB необходимо отрегулировать, как описано в здесь.

У нас также есть следующие отношения:

случай 1: UOPS_ISSUED.STALL_CYCLES = ~ RESOURCE_STALLS.ANY = ~ 4.4c / итер
случай 2: UOPS_ISSUED.STALL_CYCLES = ~ RESOURCE_STALLS.ANY = ~ 7.9c / iter
случай 3: UOPS_ISSUED.STALL_CYCLES = ~ RESOURCE_STALLS.ANY = ~ 5.6c / iter

Это означает, что причина остановки проблемы заключается в том, что один или несколько требуемых ресурсов в серверной части недоступны. Таким образом, мы можем с уверенностью исключить из рассмотрения весь фронтенд. В случаях 1 и 2 этим ресурсом является RS. В случае 3 киоски из-за RS составляют около 20% всех остановок ресурсов 1.

Давайте сосредоточимся теперь на случае 1. Всего существует 4 мупа неиспользуемых доменов: 1 моп нагрузки, 1 STA, 1 STD и 1 дек / jne. Нагрузка и мопы STA зависят от предыдущей мопы загрузки. Всякий раз, когда LSD выдает группу мопов, STD и мопы перехода могут быть отправлены в следующем цикле, поэтому следующий цикл не вызовет событие остановки выполнения. Однако самая ранняя точка, в которой могут быть отправлены мопы загрузки и STA, находится в том же цикле, в котором результат загрузки записывается обратно. Корреляция между CYCLES_NO_EXECUTE и STALLS_LDM_PENDING указывает, что причина, по которой не было бы готовых к выполнению мопов, заключается в том, что все мопы, находящиеся в RS, ждут, пока L1 обслужит ожидающие запросы загрузки. В частности, половина мопов в RS являются мопами загрузки, а другая половина - STA, и все они ожидают завершения загрузки соответствующей предыдущей итерации. LSD.CYCLES_3_UOPS показывает, что LSD ожидает, пока в RS не будет по крайней мере 4 свободных входа, только затем он выдает группу мопов, составляющих полную итерацию. В следующем цикле будут отправлены два таких мопа, освобождая тем самым 2 записи RS 2. Другой должен будет дождаться завершения загрузки, от которой они зависят. Скорее всего, загрузка завершена в программном порядке. Следовательно, LSD ожидает, пока STA и загрузочные мопы самой старой итерации, которая еще не выполнена, покинут RS. Таким образом, UOPS_ISSUED.STALL_CYCLES + 1 = ~ средняя задержка загрузки 3. Можно сделать вывод, что средняя задержка загрузки в случае 1 составляет 5,4 с. Большая часть этого относится к случаю 2, за исключением одного отличия, которое я вскоре объясню.

Поскольку мопы в каждой итерации образуют цепочку зависимостей, у нас также есть:

cycles = ~ средняя задержка загрузки.

Следовательно:

cycles = ~ UOPS_ISSUED.STALL_CYCLES + 1 = ~ средняя задержка загрузки.

В случае 1 средняя задержка загрузки составляет 5,4 с. Мы знаем, что в лучшем случае задержка кэша L1 составляет 4 с, поэтому штраф за задержку загрузки составляет 1,4 с. Но почему эффективная задержка нагрузки не 4с?

Планировщик предсказывает, что нагрузка, от которой зависят мопы, завершится с некоторой постоянной задержкой, и поэтому он соответствующим образом планирует их отправку. Если загрузка занимает больше времени по какой-либо причине (например, промах L1), мопы будут отправлены, но результат загрузки еще не получен. В этом случае мопы будут воспроизведены повторно, и количество отправленных мопов будет больше, чем общее количество выданных мопов.

Мопы нагрузки и STA могут быть отправлены только на порт 2 или 3. События UOPS_EXECUTED_PORT.PORT_2 и UOPS_EXECUTED_PORT.PORT_3 могут использоваться для подсчета количества мопов, отправленных на порт 2 и 3, соответственно.

case 1: UOPS_EXECUTED_PORT.PORT_2 + UOPS_EXECUTED_PORT.PORT_3 = ~ 2uops / iter
case 2: UOPS_EXECUTED_PORT.PORT_2 + UOPS_EXECUTED_PORT.PORT_3 = ~ 6uops / iter
case 3: UOPS_EXECUTED_PORT.PORT_2 + UOPS_EXECUTED_PORT.PORT_3 = ~ 4.2uops / iter

В случае 1 общее количество отправленных мопов AGU точно равно количеству списанных мопов AGU; повторов нет. Таким образом, планировщик никогда не ошибается. В случае 2 в среднем есть 2 повтора на муп AGU, что означает, что планировщик ошибочно предсказывает в среднем дважды для мупа AGU. Почему в случае 2 есть ошибки в предсказаниях, а в случае 1 - нет?

Планировщик будет воспроизводить ошибки в зависимости от нагрузки по любой из следующих причин:

  • Отсутствует кеш L1.
  • Неправильное предсказание разрешения неоднозначности.
  • Нарушение согласованности памяти.
  • Попадание в кэш L1, но есть трафик L1-L2.
  • Неправильное предсказание номера виртуальной страницы.
  • Некоторые другие (недокументированные) причины.

Первые 5 причин можно окончательно исключить с помощью соответствующих событий производительности. Патрик Фэй (Intel) говорит следующее:

Наконец, да, при переключении между загрузкой и хранением существует «несколько» циклов простоя. Мне сказали, что я не буду более конкретным, чем «несколько».
...
SNB может читать и записывать данные в разные банки в одном и том же цикле.

Я нахожу эти утверждения, возможно, намеренно, несколько двусмысленными. Первое утверждение предполагает, что загрузка и сохранение в L1 никогда не могут полностью перекрываться. Второй предполагает, что загрузка и сохранение могут выполняться в одном и том же цикле только при наличии разных банков. Хотя нахождение в разных банках может быть ни необходимым, ни достаточным условием. Но одно можно сказать наверняка: если есть одновременные запросы на загрузку и сохранение, загрузка (и сохранение) может быть отложена на один или несколько циклов. Это объясняет средний штраф 1,4 с на задержку загрузки в случае 1.

Существует разница между случаем 1 и случаем 2. В случае 1 мопы STA и нагрузки, зависящие от одной и той же мопы нагрузки, выдаются вместе в одном и том же цикле. С другой стороны, в случае 2 STA и мопы загрузки, которые зависят от одной и той же мп нагрузки, принадлежат двум разным группам задач. Время простоя задачи на итерацию будет по существу равно времени, необходимому для последовательного выполнения одной загрузки и вывода из эксплуатации одного хранилища. Вклад каждой операции можно оценить с помощью CYCLE_ACTIVITY.STALLS_LDM_PENDING. Для выполнения STA uop требуется один цикл, поэтому хранилище может выйти из эксплуатации в цикле, который следует сразу за тем, в котором отправляется STA.

Средняя задержка загрузки составляет CYCLE_ACTIVITY.STALLS_LDM_PENDING + 1 цикл (цикл, в котором отправляется загрузка) + 1 цикл (цикл, в котором отправляется моп перехода). Нам нужно добавить 2 цикла к CYCLE_ACTIVITY.STALLS_LDM_PENDING, потому что в этих циклах нет остановок выполнения, но они составляют часть общей задержки нагрузки. Это равно 6,8 + 2 = 8,8 цикла = ~ cycles.

Во время выполнения первой дюжины (или около того) итераций в каждом цикле RS будут выделяться команды перехода и STD. Они всегда будут отправляться на выполнение в цикле, который следует за циклом выпуска. В какой-то момент RS заполнится, и все записи, которые еще не были отправлены, будут STA и загрузочными мопами, которые ждут завершения загрузочных мопов соответствующих предыдущих итераций (обратная запись их результатов). Таким образом, распределитель остановится, пока не наберется достаточно свободных записей RS для выполнения всей итерации. Предположим, что самый старый uop загрузки записал свой результат в цикле T + 0. Я буду называть итерацию, которой принадлежит этот uop загрузки, как текущую итерацию. Произойдет следующая последовательность событий:

В цикле T + 0: отправить команду STA текущей итерации и нагрузку следующей итерации. В этом цикле нет распределения, потому что не хватает записей RS. Этот цикл считается циклом остановки выделения, но не циклом остановки выполнения.

В цикле T + 1: моп STA завершает выполнение, и хранилище удаляется. Распределяются мопы следующей итерации. Этот цикл считается циклом остановки выполнения, но не циклом остановки выделения.

В цикле T + 2: отправляются только что назначенные команды перехода и STD. Этот цикл считается циклом остановки выделения, но не циклом остановки выполнения.

В циклах от T + 3 до T + 3 + CYCLE_ACTIVITY.STALLS_LDM_PENDING - 2: все эти циклы учитываются как циклы остановки при выполнении и выделении. Обратите внимание, что здесь CYCLE_ACTIVITY.STALLS_LDM_PENDING - 1 цикл.

Следовательно, UOPS_ISSUED.STALL_CYCLES должно быть равно 1 + 0 + 1 + CYCLE_ACTIVITY.STALLS_LDM_PENDING - 1. Проверим: 7.9 = 1 + 0 + 1 + 6.8-1.

Следуя рассуждениям по случаю 1, cycles должно быть равно UOPS_ISSUED.STALL_CYCLES + 1 = 7,9 + 1 = ~ фактически измеренному cycles. Штраф, понесенный при выполнении загрузки и сохранения в одно и то же время, на 3,6 с выше, чем в случае 1. Это как если бы загрузка ожидала фиксации сохранения. Думаю, это также объясняет, почему в случае 2 есть повторы, а в случае 1 - нет.

В случае 3 есть 1 STD, 1 STA, 2 нагрузки и 1 скачок. Все мопы одной итерации могут быть распределены в одном цикле, потому что полоса пропускания IDQ-RS составляет 4 слитых мопа за цикл. При въезде в РС мопы распадаются. Для отправки 1 STD требуется 1 цикл. Прыжок тоже занимает 1 цикл. Есть три упа AGU, но только 2 порта AGU. Таким образом, для отправки мопов AGU требуется 2 цикла (по сравнению с 1 в случаях 1 и 2). Группа отправленных БМП AGU будет одной из следующих:

  • Второй uop загрузки и STA uop той же итерации. Они зависят от первой нагрузки на той же итерации. Используются оба порта AGU.
  • Первая группа загрузки следующей итерации может быть отправлена ​​в следующем цикле. Это зависит от загрузки предыдущей итерации. Используется только один из двух портов AGU.

Поскольку требуется еще один цикл, чтобы освободить достаточно записей RS для размещения всей группы задач, UOPS_ISSUED.STALL_CYCLES + 1 - 1 = UOPS_ISSUED.STALL_CYCLES = ~ средняя задержка загрузки = ~ 5,6c, что очень близко к случаю 1. Штраф составляет около 1.6c. Это объясняет, почему в случае 3 по сравнению со случаями 1 и 2 каждый моп AGU отправляется в среднем 1,4 раза.

Опять же, поскольку требуется больше цикла, чтобы освободить достаточно записей RS для размещения всей группы задач:

cycles = ~ средняя задержка загрузки + 1 = 6,6 с / л, что на самом деле точно соответствует cycles, измеренному в моей системе.

Подробный анализ, аналогичный тому, что был в случае 2, может быть проведен и в случае 3. В случае 3 выполнение STA перекрывается с задержкой второй загрузки. Задержки обеих нагрузок также в основном перекрываются.

Я не знаю, почему штрафы разные в разных случаях. Нам нужно знать, как именно устроен кеш L1D. В любом случае, я достаточно уверен, что есть штраф в виде «нескольких циклов простоя» на задержку загрузки (и задержку хранилища), чтобы опубликовать этот ответ.


Сноски

(1) Остальные 80% времени тратятся на остановку нагрузки на матрицу. Эта структура почти не упоминается в руководстве. Он используется для указания зависимостей между uops и load uops. по оценкам содержит 32 записи о SnB и IvB. Нет задокументированного события производительности, которое могло бы подсчитывать только киоски на LM. Все задокументированные события остановки ресурсов равны нулю. В случае 3 на итерацию приходится 3 из 5 мопов, которые зависят от предыдущей загрузки, поэтому, скорее всего, LM будет заполнен до любой из других структур. «Эффективное» количество записей RS оценивается примерно в 51 и 48 на IvB и SnB соответственно.

(2) Я мог бы сделать здесь безобидное упрощение. См. Возможно ли, чтобы событие RESOURCE_STALLS.RS произошло, даже если RS не полностью заполнен?.

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


Метод анализа 2. Использование средства мониторинга производительности задержки нагрузки

Я придумал другой метод анализа кода. Этот метод намного проще, но менее точен. Однако, по сути, это приводит нас к такому же выводу.

Альтернативный метод основан на MEM_TRANS_RETIRED.LOAD_LATENCY_* событиях производительности. Эти события являются особенными в том смысле, что их можно подсчитать только на уровне повторения p (см. PERF STAT не считает загрузки памяти, а считает количество хранилищ памяти).

Например, MEM_TRANS_RETIRED.LOAD_LATENCY_GT_4 подсчитывает количество нагрузок, задержка которых превышает 4 основных цикла "случайно" выбранной выборки всех выполненных нагрузок. Задержка измеряется следующим образом. Цикл, в котором нагрузка отправляется в первый раз, является первым циклом, который рассматривается как часть задержки загрузки. Цикл, в котором результат загрузки записывается обратно, является последним циклом, который считается частью задержки. Следовательно, повторы учитываются. Кроме того, начиная с SnB (как минимум), все нагрузки имеют задержки более 4 циклов в соответствии с этим определением. Поддерживаемый минимальный порог задержки составляет 3 цикла.

Case 1
Lat Threshold  | Sample Count
 3             | 1426934
 4             | 1505684
 5             | 1439650
 6             | 1032657      << Drop 1
 7             |   47543      << Drop 2
 8             |   57681
 9             |   60803
10             |   76655
11             |     <10      << Drop 3

Case 2
Lat Threshold  | Sample Count
 3             | 1532028
 4             | 1536547
 5             | 1550828
 6             | 1541661
 7             | 1536371
 8             | 1537337
 9             | 1538440
10             | 1531577
11             |     <10      << Drop

Case 3
Lat Threshold  | Sample Count
 3             | 2936547
 4             | 2890162
 5             | 2921158
 6             | 2468704      << Drop 1
 7             | 1242425      << Drop 2
 8             | 1238254
 9             | 1249995
10             | 1240548
11             |     <10      << Drop 3

Важно понимать, что эти числа представляют собой количество загрузок случайно выбранной выборки всех загрузок. Например, из общего размера выборки всех загрузок 10 миллионов, и только 1 миллион из них имеет задержку, превышающую заданный порог, тогда измеренное значение будет 1 миллион. Однако общее количество выполненных загрузок может составить 1 миллиард. Следовательно, абсолютные значения сами по себе не очень значимы. Что действительно важно, так это образец для разных пороговых значений.

В случае 1 происходит три значительных падения количества нагрузок, задержка которых превышает определенный порог. Мы можем сделать вывод, что нагрузки, задержка которых равна или меньше 6 циклов, являются наиболее распространенными, нагрузки, задержка которых равна или меньше 7 циклов, но больше 6 циклов, являются вторыми по частоте, а большинство других нагрузок имеют задержку между 8-11 циклов.

мы уже знаем, что минимальная задержка составляет 4 цикла. Учитывая эти числа, разумно оценить среднюю задержку загрузки где-то между 4 и 6 циклами, но ближе к 6, чем 4. Из метода 1 мы знаем, что средняя задержка загрузки на самом деле составляет 5,4 с. Таким образом, мы можем сделать довольно хорошую оценку, используя эти числа.

В случае 2 мы можем сделать вывод, что у большинства нагрузок задержка меньше или равна 11 циклам. Средняя задержка загрузки, вероятно, также намного больше 4, учитывая согласованность измеренного количества нагрузок в широком диапазоне пороговых значений задержки. Таким образом, оно находится между 4 и 11, но ближе к 11, чем 4. Из метода 1 мы знаем, что средняя задержка загрузки на самом деле составляет 8,8 с, что близко к любой разумной оценке, основанной на этих числах.

Случай 3 аналогичен случаю 1, и на самом деле фактическая средняя задержка загрузки, определенная с помощью метода 1, почти одинакова для этих двух случаев.

Выполнить измерения с помощью MEM_TRANS_RETIRED.LOAD_LATENCY_* легко, и такой анализ может выполнить человек, мало знакомый с микроархитектурой.

person Hadi Brais    schedule 20.01.2019
comment
Знаем ли мы, действительно ли мопы STD зависят от мопов STA, или они могут работать в любом порядке / параллельно? Было бы разумно, если бы STA пришлось зарезервировать место в буфере хранилища для STD, предположительно упростив дизайн. (И в этом больше смысла, чем в том, чтобы зависимость пошла другим путем, потому что вы хотите обнаружить перекрытие / переадресацию хранилища до того, как данные хранилища будут готовы.) Но знаем ли мы наверняка? Планировщик или распределитель может выбрать запись буфера хранилища для использования обоими вместо того, чтобы требовать какой-либо пересылки результата от STA к STD. - person Peter Cordes; 21.01.2019
comment
@PeterCordes Да, мы точно знаем, что STD и STA могут быть отправлены в любом порядке. Распределение уже произведено на этапе распределения. Это упоминается во многих (старых) документах Intel и может быть подтверждено экспериментально. - person Hadi Brais; 21.01.2019
comment
Ах, я пытался быстро прочитать части этого подробного ответа, который, как я ожидал, я уже понял, и упустил из виду, как вы это объясняли. Итак, как я и ожидал, STD и прыгающие мопы могут отправляться сразу после того, как они выпускают, и это только STA и загрузочные мопы, где вы говорите о текущей итерации. Я думал, вы говорили, что мопы STD ждут мопов STA той же итерации, но при более внимательном чтении вы говорите не об этом. Возможно, добавление большего количества слов, таких как самый старый STA и только что выделенный STD / прыжок, поможет будущим читателям отслеживать, что происходит. - person Peter Cordes; 21.01.2019
comment
Я не совсем слежу за выводом высокого уровня. Большинство этих архитектур могут поддерживать 1 загрузку и 1 хранилище за цикл, что подразумевает, что не всегда происходит повторное воспроизведение нагрузки, когда они пытаются выполнить в одном и том же цикле: действительно, в этом сценарии они всегда выполняется в том же цикле. Так что общих проблем с загрузками и магазинами нет. Точно так же не может быть задержки в несколько циклов при переключении между загрузкой и сохранением, поскольку снова вы можете выполнять 1 и 1 каждый цикл, что вообще несовместимо с какой-либо задержкой (если задержка означает циклы, в которых L1 ничего не может сделать). - person BeeOnRope; 01.02.2019
comment
Я думаю, что ключевым моментом является погоня за указателем и специальная 4-тактная оптимизация поиска указателя. Мы знаем, что эта оптимизация применяется только тогда, когда виртуальная страница предсказана правильно, и что она также работает, если нет промежуточной операции ALU, изменяющей адрес. Вполне возможно, что это работает только тогда, когда последовательные нагрузки идут на один и тот же порт (p2 или p3), поскольку результат входящей нагрузки, возможно, придется немедленно передать обратно в AGU. Теперь в случае, если существует конкурирующая операция магазина, возможно, вместо этого используется AGU, и оптимизация прерывается, что приводит к повторному воспроизведению за 9 циклов. - person BeeOnRope; 01.02.2019
comment
Или, возможно, проблема в том, что и хранилище, и последующая загрузка ждут результата загрузки. В случае отслеживания указателя с 4 циклами, возможно, результат загрузки не готов к трансляции в обходной сети по оптимизированному 4-тактному пути, поэтому требуется дополнительный цикл, который нарушает прогноз планировщика на 4 цикла? У них обоих есть разные проблемы, например, почему смещение имеет значение. Я не нашел подробностей о том, какие именно смещения показывают поведение? - person BeeOnRope; 01.02.2019
comment
@Hadi. Мне понравился список. Планировщик будет воспроизводить ошибки в зависимости от нагрузки по любой из следующих причин - где вы его нашли или собрали сами из разных источников? - person BeeOnRope; 01.02.2019
comment
@BeeOnRope Я думаю, что ответ окончательно показывает, что пропускная способность мала в основном потому, что задержка загрузки высока, и что задержка нагрузки высока, потому что хранилище несет штраф в несколько циклов нагрузки. Однако, как вы отметили, ответ не объясняет, почему и когда может произойти это наказание. Мы эмпирически определили, что адреса загрузки и хранения могут повлиять на этот штраф. Так что да, можно выполнять одно сохранение и загрузку за цикл, но не всегда. Ваши 2-е и 3-е комментарии ... - person Hadi Brais; 01.02.2019
comment
... похоже, пытается объяснить почему, что говорит о том, что вы убеждены в существовании такого штрафа, но ваш первый комментарий, похоже, оспаривает это, поэтому я немного смущен. Но, как вы сказали, это не похоже на то, что существует общая проблема, относящаяся к каждой загрузке и хранению, но определенно существует проблема, отличная от тех, которые я перечислил. Я сам составил этот список, основываясь на своих прошлых экспериментах с микроархитектурами до SKL. Что касается того, почему и когда, необходимо сделать больше работы, которой у меня нет (пока). - person Hadi Brais; 01.02.2019
comment
Мой первый комментарий был конкретно о вашем первом предложении: при одновременном выполнении загрузки и сохранения происходит штраф в несколько циклов. Я воспринял это как общее утверждение. Однако неверно, что в этом случае обычно возникает штраф (повторное воспроизведение), на самом деле это довольно необычно, поэтому эффект в этом вопросе является особенным до такой степени, что я впервые узнал об этом здесь. Возможно, вы имели в виду это в менее общем смысле, например, в этом конкретном случае, хранилище и загрузка мешают таким образом, чтобы заставить загрузку воспроизводиться и увеличивать задержку .... - person BeeOnRope; 02.02.2019
comment
Я согласен с вами, что задержка загрузки больше, но исходные вопросы уже показали это: эталонный тест фактически является измерением задержки загрузки. Таким образом, если сказать, что цикл погони за указателем занимает X циклов, потому что задержка загрузки в этом случае равна X, это мало что значит, не так ли? Настоящий вопрос в том, почему увеличивается задержка - и мы знаем вероятный ответ 1-го уровня: происходит повтор. Итак, второй вопрос: почему происходит повтор? Остальной анализ, хотя и интересный, в основном просто показывает, что ничего сверхъестественного не происходит вне повтора: то, что вы видите, то и получаете ... - person BeeOnRope; 02.02.2019
comment
@BeeOnRope Я не про вас, но когда я впервые прочитал вопрос, я был удивлен, увидев, что пропускная способность цикла составляет 5,4c, 9c и 6,8 для трех разных случаев. Совершенно не очевидно (по крайней мере, для меня), откуда берутся эти числа, какова эффективная задержка загрузки в каждом из случаев и как она влияет на общую производительность. Эти цифры странные для таких петель. Мой анализ показывает, что все случаи фактически ограничены задержкой загрузки, но просто задержка больше, чем ожидалось (4c), и варьируется. Что касается того, почему, мы можем ... - person Hadi Brais; 02.02.2019
comment
... догадываюсь, но мы не можем знать наверняка без доступа к деталям реализации. Рассмотрим аналогичный вопрос о простом цикле загрузки с пропускной способностью 4c. Ожидаете ли вы, что ответ объяснит, почему задержка загрузки равна 4c? Мы также можем сделать ряд хороших предположений, но мы не можем знать наверняка, не имея доступа к деталям реализации. Если у вас есть эмпирический метод, который мы можем использовать, чтобы с большой уверенностью ответить на вопрос «почему», я все уши .... - person Hadi Brais; 02.02.2019
comment
С другой стороны, мы можем ответить на вопрос, когда проведем достаточное количество экспериментов. В любом случае, я поясню это конкретное предложение в верхней части ответа. - person Hadi Brais; 02.02.2019
comment
Да, я был удивлен цифрами: я ожидал, что все эти циклы будут выполняться за 4 цикла на итерацию. Я думаю, что средняя эффективная задержка загрузки здесь относительно очевидна: это точно время итерации каждого теста. Вам не нужен какой-либо глубокий анализ, чтобы понять это, потому что эталонный тест, по сути, является каноническим эталоном для задержки загрузки (серии зависимых нагрузок). Таким образом, утверждение, что задержка загрузки составляет 5,4 с для теста, который занимает 5,4 цикла циклов для выполнения серии зависимых нагрузок, мало что говорит: единственная интересная часть - это почему? - person BeeOnRope; 02.02.2019
comment
Для простого цикла, который на самом деле занимает 4c, ответ будет прост: это известная и задокументированная задержка загрузки на Intel, и этот тип цикла связан с задержкой загрузки, и поэтому все в точности так, как ожидалось. Это случаи, когда задержка загрузки составляет 5,4 с или 9 с или что-то еще, я думаю, неявно возникает вопрос, почему 5,4 или 9 с, а не 4? - person BeeOnRope; 02.02.2019