TL; DR: в неиспользованном домене всего 5 мопов (см .: Режимы микросинтеза и адресации ). Детектор потока цикла на Ivy Bridge не может распределять мопы по границам тела цикла (см .: Снижается ли производительность при выполнении циклов, число мопов которых не кратно ширине процессора? ), поэтому для выделения требуется два цикла одна итерация. Цикл на самом деле работает на 2.3c / iter на двойной розетке Xeon E5-2680 v2 (10 ядер на сокет против ваших 12), так что это близко к лучшему, что может быть сделано с учетом узкого места внешнего интерфейса.
Предварительные выборщики работали очень хорошо, и большую часть времени цикл не связан с памятью. Копирование 1 байта за 2 цикла происходит очень медленно. (gcc сделал плохую работу и должен был дать вам цикл, который мог бы выполняться с 1 итерацией за такт. Без оптимизации по профилю, даже -O3
не позволяет -funroll-loops
, но есть приемы, которые он мог бы использовать ( например, подсчет отрицательного индекса до нуля или индексирование нагрузки относительно хранилища и увеличение указателя назначения), что привело бы к уменьшению цикла до 4 моп.)
Дополнительные .3 цикла на итерацию медленнее, чем узкое место в среднем переднего конца. - это , вероятно, из-за неудачных попыток предварительной выборки (возможно, на границах страницы), или, возможно, из-за сбоев страниц и пропусков TLB в этом тест, который выполняется в статически инициализированной памяти в разделе .data
.
В цикле есть две зависимости данных. Во-первых, инструкция сохранения (в частности, STD uop) зависит от результата инструкции загрузки. Во-вторых, инструкции для сохранения и загрузки зависят от add rax, 0x1
. На самом деле add rax, 0x1
зависит также и от самого себя. Поскольку задержка add rax, 0x1
составляет один цикл, верхняя граница производительности цикла составляет 1 цикл на итерацию.
Поскольку хранилище (STD) зависит от нагрузки, оно не может быть отправлено с RS до тех пор, пока загрузка не завершится, что занимает не менее 4 циклов (в случае попадания L1). Кроме того, есть только один порт, который может принимать STOP-мопы, но до двух нагрузок можно завершить за цикл на Ivy Bridge (особенно в случае, если две нагрузки относятся к линиям, которые находятся в кэше L1, и не возникает конфликт банков), приводя к дополнительному раздору. Тем не менее, RESOURCE_STALLS.ANY
показывает, что фактическое RS никогда не заполняется. IDQ_UOPS_NOT_DELIVERED.CORE
подсчитывает количество неиспользованных слотов выдачи. Это равно 36% всех слотов. Событие LSD.CYCLES_ACTIVE
показывает, что ЛСД используется для доставки мопов большую часть времени. Однако LSD.CYCLES_4_UOPS
/ LSD.CYCLES_ACTIVE
= ~ 50% показывает, что примерно в 50% циклов на RS поступает менее 4 моп. RS не будет заполнен из-за неоптимальной пропускной способности распределения.
Счет stalled-cycles-frontend
соответствует UOPS_ISSUED.STALL_CYCLES
, который подсчитывает количество остановок распределения, связанных как с внешними, так и с задними частями. Я не понимаю, как UOPS_ISSUED.STALL_CYCLES
связано с количеством циклов и других событий.
Количество LLC-loads
включает в себя:
- Вся нагрузка по требованию запрашивает к L3 независимо от того, попадет или нет запрос в L3 и, в случае пропуска, независимо от источника данных. Это также включает запросы загрузки по требованию от аппаратного средства просмотра страниц. Мне не ясно, учитываются ли запросы на загрузку от средства предварительной загрузки следующей страницы.
- Все запросы на чтение данных аппаратной предварительной выборки, сгенерированные предварительной выборкой L2, где целевая линия должна быть помещена в L3 (то есть в L3 или в оба в L3 и L2, но не только в L2). Аппаратные запросы чтения данных устройства предварительной выборки L2, где линия должна быть размещена только в L2, не включены. Обратите внимание, что запросы предварительных сборщиков L1 поступают в L2 и влияют на них и могут вызывать предварительные выборщики L2, то есть они не пропускают L2.
LLC-load-misses
является подмножеством LLC-loads
и включает только те события, которые пропущены в L3. Оба подсчитаны на ядро.
Существует важное различие между подсчетом запросов (гранулярность строки кэша) и подсчетом инструкций загрузки или загрузок мопов (с использованием MEM_LOAD_UOPS_RETIRED.*
). Кэши L1 и L2 кэшируют запросы загрузки сквоша в одну и ту же строку кэша, поэтому многочисленные пропуски в L1 могут привести к одному запросу к L3.
Оптимальная производительность может быть достигнута, если все хранилища и нагрузки попадут в кэш L1. Поскольку размер используемого вами буфера составляет 1 ГБ, цикл может вызвать максимум 1 ГБ / 64 = ~ 17 М запросов загрузки L3. Тем не менее, ваше LLC-loads
измерение, 83M, намного больше, возможно, из-за кода, отличного от цикла, который вы показали в вопросе. Другая возможная причина заключается в том, что вы забыли использовать суффикс :u
для подсчета только событий пользовательского режима.
Мои измерения как на IvB, так и на HSW показывают, что LLC-loads:u
ничтожно мал по сравнению с 17M. Однако большинство нагрузок L3 являются пропущенными (т. Е. LLC-loads:u
= ~ LLC-loads-misses:u
). CYCLE_ACTIVITY.STALLS_LDM_PENDING
показывает, что общее влияние нагрузок на производительность незначительно. Кроме того, мои измерения показывают, что цикл работает на 2,3c / iter на IvB (против 1,5c / iter на HSW), что говорит о том, что одна нагрузка выдается каждые 2 цикла. Я думаю, что неоптимальная пропускная способность распределения является главной причиной этого. Обратите внимание, что условия псевдонимов 4K (LD_BLOCKS_PARTIAL.ADDRESS_ALIAS
) практически отсутствуют. Все это означает, что средства предварительной выборки довольно хорошо справились с задачей скрытия задержки доступа к памяти для большинства нагрузок.
Счетчики на IvB, которые можно использовать для оценки производительности аппаратных средств предварительной выборки:
Ваш процессор имеет два устройства предварительной выборки данных L1 и два устройства предварительной выборки данных L2 (один из них может выполнять предварительную выборку как в L2, так и / или в L3). Устройство предварительной выборки может быть неэффективным по следующим причинам:
- Условие запуска не выполнено. Обычно это происходит потому, что шаблон доступа еще не распознан.
- Предварительная выборка сработала, но предварительная выборка была бесполезной.
- Предварительный выборщик активирован на полезную строку, но строка была заменена перед использованием.
- Предварительная выборка сработала в полезной строке, но запросы по требованию уже достигли кеша и пропустили. Это означает, что запросы спроса были выполнены быстрее, чем способность средства предварительной выборки своевременно реагировать. Это может произойти в вашем случае.
- Предварительная выборка была запущена на полезную строку (которой нет в кэше), но запрос пришлось отбросить, так как MSHR не был доступен для удержания запроса. Это может произойти в вашем случае.
Количество пропущенных запросов на L1, L2 и L3 является хорошим показателем того, насколько хорошо работают сборщики. Все пропуски L3 (как считается LLC-load-misses
) также обязательно являются пропусками L2, поэтому количество пропусков L2 больше, чем LLC-load-misses
. Также все пропуски по требованию L2 - это обязательно пропуски по L1.
На Ivy Bridge вы можете использовать события производительности LOAD_HIT_PRE.HW_PF
и CYCLE_ACTIVITY.CYCLES_*
(в дополнение к событиям промаха), чтобы узнать больше о том, как выполнялись сборщики предварительной выборки, и оценить их влияние на производительность. Важно измерить CYCLE_ACTIVITY.CYCLES_*
событий, потому что даже если количество пропусков было на первый взгляд высоким, это не обязательно означает, что пропуски являются основной причиной снижения производительности.
Обратите внимание, что средства предварительной выборки L1 не могут выдавать спекулятивные запросы RFO. Поэтому большинство операций записи, достигающих L1, будут фактически отсутствовать, требуя выделения LFB для каждой строки кэша на L1 и потенциальности других уровней.
Код, который я использовал, следующий:
BITS 64
DEFAULT REL
section .data
bufdest: times COUNT db 1
bufsrc: times COUNT db 1
section .text
global _start
_start:
lea rdi, [bufdest]
lea rsi, [bufsrc]
mov rdx, COUNT
mov rax, 0
.loop:
movzx ecx, byte [rsi+rax*1]
mov byte [rdi+rax*1], cl
add rax, 1
cmp rdx, rax
jnz .loop
xor edi,edi
mov eax,231
syscall