Tl; DR: для этих трех случаев возникает штраф в несколько циклов при одновременном выполнении загрузки и сохранения. Задержка загрузки находится на критическом пути во всех трех случаях, но штраф в разных случаях различен. Случай 3 примерно на цикл больше, чем случай 1 из-за дополнительной нагрузки.
Метод анализа 1. Использование событий производительности останова
Мне удалось воспроизвести ваши результаты для всех трех случаев на IvB и SnB. Полученные мной числа находятся в пределах 2% от ваших. Количество циклов, необходимых для выполнения одной итерации для случаев 1, 2 и 4, составляет 5,4, 8,9 и 6,6 соответственно.
Начнем с внешнего интерфейса. События производительности LSD.CYCLESUOPS_ISSUED.STALL_CYCLES
UOPS
и LSD.CYCLESLSD.CYCLES_ACTIVE
UOPS
показывают, что в основном все мопы исходят от 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
mov
приводит к конфликту между блоками AGU, поэтому производительность должна быть немного хуже, чем в случае 1, поэтому 6.8c / iter имеет смысл. Я не могу придумать очевидного объяснения случая 2, поэтому мы должны исследовать больше. Например, добавьтеmov qword [rax+8], 8
непосредственно перед (вне) цикла и посмотрите, как это изменит производительность. Еще вы можете попробовать выровнять цикл по 32-байтовой границе. - person Hadi Brais   schedule 08.01.2019mov
перед циклом или выравнивание цикла не имеет значения. Но смещение 8 в[rax+8]
может сыграть здесь важную роль, см. Мою правку. - person user10865622   schedule 09.01.2019LD_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+8
, но могут быть для+128
. Но+32
все еще находится в той же строке, записывая другой банк, и не пересекает линию или границу банка, потому чтоbuf
выровнен. - person Peter Cordes   schedule 10.01.2019[rel buf+8]
приводит к тому, что случай 2 запускается на SKL со скоростью 4c на каждого пользователя. ЦП может запускать мопы адресов хранилища заранее, если интерфейсная часть вообще работает впереди, и быстрее обнаруживать, что загрузка и сохранение не перекрываются. Я думаю, что читал, что процессоры Intel используют динамическое предсказание для устранения неоднозначности памяти. (т.е. есть кеш-предиктор, который запоминает, что эта загрузка не нуждалась в переадресации хранилища.) Если это действительно так, единственный эффект от того, чтобы сделать адрес зависимым, состоит в том, что мопы с адресом хранилища не могут запускаться так рано. (Я также пробовал сabs buf
, чтобы он мог плавиться: то же самое). - person Peter Cordes   schedule 10.01.2019[rel buf+8]
на IvyBridge, все случаи становятся 4c / iter, так что это связано с зависимыми магазинами. Из любопытства, как часто на практике случаются подобные странности с микроархитектурой? Этот код выглядит настолько простым для более сложного реального кода, что мне интересно, действительно ли возможно предсказать количество циклов в критической части. - person user10865622   schedule 10.01.2019STALLS_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[rax+8]
на[rax+64]
или больше, case1 и case2 станут 4.3c / iter и _3 _ + _ 4 _ + _ 5_ = 2B, как и ожидалось. - person Hadi Brais   schedule 10.01.2019STALLS_LDM_PENDING
мог уловить этот эффект. Результаты OP предполагают, что эта проблема может существовать на IvB, но с другим влиянием на производительность ... - person Hadi Brais   schedule 10.01.2019