Tl; DR: В этих трех случаях при выполнении загрузки и хранения одновременно налагается штраф в несколько циклов. Задержка загрузки находится на критическом пути во всех трех случаях, но штраф в разных случаях различен. Случай 3 примерно на один цикл выше, чем случай 1 из-за дополнительной нагрузки.
Метод анализа 1: Использование событий производительности сваливания
Мне удалось воспроизвести ваши результаты по всем трем случаям на IvB и SnB. Числа, которые я получил, находятся в пределах 2% от ваших чисел. Число циклов, необходимое для выполнения одной итерации в случаях 1, 2 и 4, составляет 5,4, 8,9 и 6,6 соответственно.
Давайте начнем с внешнего интерфейса. События производительности LSD.CYCLES_4_UOPS
и LSD.CYCLES_3_UOPS
показывают, что в основном все мопы выдаются из LSD. Кроме того, эти события вместе с LSD.CYCLES_ACTIVE
показывают, что в каждом цикле, в котором ЛСД не останавливается, выдается 3 мопа в случаях 1, а в случае 3 выдается 4 мопа. Другими словами, как и ожидалось, мопы каждой итерации выпускаются вместе в одной группе в одном цикле.
Во всех следующих отношениях знак "= ~" означает, что разница находится в пределах 2%. Начну со следующего эмпирического наблюдения:
UOPS_ISSUED.STALL_CYCLES
+ LSD.CYCLES_ACTIVE
= ~ cycles
Обратите внимание, что количество LSD-событий на SnB необходимо корректировать, как описано в здесь .
У нас также есть следующие отношения:
case 1: UOPS_ISSUED.STALL_CYCLES
= ~ RESOURCE_STALLS.ANY
= ~ 4.4c / iter
дело 2: UOPS_ISSUED.STALL_CYCLES
= ~ RESOURCE_STALLS.ANY
= ~ 7,9c / iter
дело 3: UOPS_ISSUED.STALL_CYCLES
= ~ RESOURCE_STALLS.ANY
= ~ 5,6 с / итера
Это означает, что причина проблемы в том, что один или несколько необходимых ресурсов в бэкэнде недоступны. Таким образом, мы можем с уверенностью исключить весь интерфейс из рассмотрения. В случаях 1 и 2 этим ресурсом является RS. В случае 3 киоски из-за RS составляют около 20% всех киосков ресурсов 1 .
Давайте теперь сосредоточимся на случае 1. Всего имеется 4 неиспользуемых мопа домена: 1 загрузочный моп, 1 STA, 1 STD и 1 dec / 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 равна 4c, поэтому штраф за задержку загрузки составляет 1.4c. Но почему эффективная задержка загрузки не равна 4с?
Планировщик прогнозирует, что нагрузка, от которой зависят мопы, будет завершена в течение некоторой постоянной задержки, и поэтому он будет планировать их отправку соответствующим образом. Если по какой-либо причине загрузка занимает больше времени, чем это (например, промах L1), мопы будут отправлены, но результат загрузки еще не наступил. В этом случае мопы будут воспроизведены, и количество отправленных мопов будет больше, чем общее количество выпущенных мопов.
Загрузка и STA-мопы могут быть отправлены только на порт 2 или 3. События UOPS_EXECUTED_PORT.PORT_2
и UOPS_EXECUTED_PORT.PORT_3
могут использоваться для подсчета количества мопов, отправленных на порты 2 и 3 соответственно.
дело 1: UOPS_EXECUTED_PORT.PORT_2
+ UOPS_EXECUTED_PORT.PORT_3
= ~ 2 моп / итер
дело 2: UOPS_EXECUTED_PORT.PORT_2
+ UOPS_EXECUTED_PORT.PORT_3
= ~ 6 моп / итер
случай 3: UOPS_EXECUTED_PORT.PORT_2
+ UOPS_EXECUTED_PORT.PORT_3
= ~ 4,2 моп / итэр
В случае 1 общее количество отправленных моп AGU точно равно количеству удаленных моп AGU; нет повторов. Так что планировщик никогда не прогнозирует. В случае 2, в среднем, 2 повторных воспроизведения для каждой операции AGU, а это означает, что планировщик в среднем дважды прогнозирует ошибочные действия для каждой операции AGU. Почему в случае 2 есть неправильные прогнозы, а в случае 1 - нет?
Планировщик будет воспроизводить моп в зависимости от нагрузки по любой из следующих причин:
- Отсутствует кэш L1.
- Неправильное предсказание неоднозначности памяти.
- Нарушение целостности памяти.
- Удар кеша L1, но есть трафик L1-L2.
- Ошибочное прогнозирование номера виртуальной страницы.
- Некоторые другие (недокументированные) причины.
Первые 5 причин могут быть окончательно исключены с помощью соответствующих событий производительности. Патрик Фэй (Intel) говорит следующее:
Наконец, да, при переключении между
загрузить и магазин. Мне сказали не быть более конкретным, чем «несколько».
...
SNB может читать и писать разные банки в одном цикле.
Я нахожу эти утверждения, возможно, намеренно, немного двусмысленными. Первое утверждение предполагает, что загрузка и сохранение в L1 никогда не могут полностью перекрываться. Второй предполагает, что загрузка и хранение могут быть выполнены в одном и том же цикле, только если они находятся в разных банках. Хотя нахождение в разных банках не может быть ни необходимым, ни достаточным условием. Но одно можно сказать наверняка: если есть параллельные запросы на загрузку и сохранение, загрузка (и хранилище) может быть отложена на один или несколько циклов. Это объясняет средний штраф 1.4c на задержку загрузки в случае 1.
Существует разница между случаем 1 и случаем 2. В случае 1 STA и нагрузочные мопы, которые зависят от одного и того же нагрузочного мопа, вырабатываются вместе в одном и том же цикле. С другой стороны, в случае 2 STA и загрузки uops, которые зависят от одной и той же загрузки нагрузки, принадлежат двум разным группам проблем. Время простоя проблемы на итерацию будет по существу равно времени, которое требуется для последовательного выполнения одной загрузки и вывода из эксплуатации одного хранилища. Вклад каждой операции можно оценить, используя CYCLE_ACTIVITY.STALLS_LDM_PENDING
. Выполнение STA-операции занимает один цикл, поэтому хранилище может завершить работу в цикле, который следует непосредственно за циклом, в котором отправляется STA.
Средняя задержка нагрузки составляет CYCLE_ACTIVITY.STALLS_LDM_PENDING
+ 1 цикл (цикл, в котором отправляется нагрузка) + 1 цикл (цикл, в котором отправляется скачок). Нам нужно добавить 2 цикла к CYCLE_ACTIVITY.STALLS_LDM_PENDING
, потому что в этих циклах нет остановок выполнения, но они составляют часть общей задержки загрузки. Это равно 6,8 + 2 = 8,8 циклов = ~ cycles
.
Во время выполнения первых дюжин (или около того) итераций прыжок и моп STD будут выделяться в RS каждый цикл. Они всегда будут отправлены для выполнения в цикле, который следует за циклом выпуска. В какой-то момент 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. Таким образом, требуется 2 цикла (по сравнению с 1 в случае 1 и 2) для отправки мопов AGU. Отправленная группа AGU будет одной из следующих:
- Вторая загрузка uop и STA uop той же итерации. Они зависят от первой загрузки uop той же итерации. Используются оба порта AGU.
- Первая загрузка в следующей итерации может быть отправлена в следующем цикле. Это зависит от загрузки предыдущей итерации. Используется только один из двух портов AGU.
Поскольку для освобождения достаточного количества записей RS для размещения всей группы вопросов требуется еще один цикл, UOPS_ISSUED.STALL_CYCLES
+ 1 - 1 = UOPS_ISSUED.STALL_CYCLES
= ~ средняя задержка загрузки = ~ 5,6 с, что очень близко к Случай 1. Штраф составляет около 1.6с. Это объясняет, почему в случае 3 по сравнению со случаями 1 и 2 каждая мера AGU отправляется в среднем в 1,4 раза.
Опять же, поскольку требуется больше цикла для освобождения достаточного количества записей RS для размещения всей группы вопросов:
cycles
= ~ средняя задержка загрузки + 1 = 6,6 с / мегатерна, что в действительности точно соответствует cycles
, как измерено в моей системе.
Подробный анализ, аналогичный анализу для случая 2, можно провести и для случая 3. В случае 3 выполнение STA перекрывается с задержкой второй загрузки. Задержки обеих нагрузок также в основном перекрываются.
Я не знаю, почему штрафы разные в разных случаях. Нам нужно знать, как именно спроектирован кэш L1D. В любом случае, я чувствую себя достаточно уверенно, что за публикацию этого ответа налагается задержка в «несколько циклов простоя» на задержку загрузки (и задержку хранилища).
Сноска
(1) Остальные 80% времени тратятся на остановку матрицы нагрузки. Эта структура едва упоминается в руководстве. Он используется для указания зависимостей между мопами и ментами загрузки. приблизительно иметь 32 записи на SnB и IvB. Не существует задокументированного события производительности, которое могло бы подсчитывать количество остановок на LM. Все задокументированные события остановки ресурса равны нулю. В случае 3 на каждую итерацию приходится 3 из 5 мопов, которые зависят от предыдущей загрузки, поэтому, скорее всего, LM будет заполнен раньше, чем любая другая структура. «Эффективное» количество записей RS, по оценкам, составляет около 51 и 48 на IvB и SnB, соответственно.
(2) Я мог бы сделать здесь безобидное упрощение. См. Возможно ли событие RESOURCE_STALLS.RS произойти, даже если RS не полностью заполнен? .
(3) Может быть полезно создать визуализацию потока uop через конвейер, чтобы увидеть, как все это сочетается. Вы можете использовать простую цепочку загрузки в качестве ссылки. Это легко для случая 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.4c. Таким образом, мы можем сделать довольно хорошую оценку, используя эти цифры.
В случае 2 мы можем сделать вывод, что у большинства нагрузок задержка меньше или равна 11 циклам. Средняя задержка нагрузки, вероятно, также намного больше, чем 4, учитывая согласованность измеренного количества нагрузок в широком диапазоне порогов задержки. Таким образом, между 4 и 11, но ближе к 11, чем 4. Мы знаем из метода 1, что средняя задержка нагрузки на самом деле составляет 8,8 с, что близко к любой разумной оценке, основанной на этих числах.
Случай 3 аналогичен случаю 1, и фактически фактическая средняя задержка загрузки, определенная с использованием метода 1, практически одинакова для этих двух случаев.
Выполнение измерений с использованием MEM_TRANS_RETIRED.LOAD_LATENCY_*
легко, и такой анализ может быть сделан кем-то, мало знакомым с микроархитектурой.