Pull to refresh

Comments 4

На синтетическом с nop'ами более-менее очевидно как они странслируются в mop'ы и как будет нагружен кэш.


А вот практический пример не особо понятен. Ок, по метрикам видим, что кэш используется неэффективно. Исходя из предыдущих тезисов статьи — это потому что mop'ы раскиданы по разным кэш-линейкам? Тогда есть вопрос — как пришли к тому, что нужно развернуть цикл? Я так полагаю что нужно было бы смоделировать a) разбивку инструкцию по mop'ам b) формирование кэша с учётом внутренней логики распределения mop'ов по линейкам. И всё это сделать глядя на C'шный код? ;)


Или просто эмпирическая догадка — а-ля замерим-ка развернутый цикл и посмотрим вдруг микроинструкции легли более четко на кэш?

И всё это сделать глядя на C'шный код? ;)


Изначально не было кода на C и я просто хотел разобраться как ведет себя µop cache в различных ситауциях. Например, известный CPU Erratum описывает неожидаемое поведение кэша при наличии jcc в конце 32-байтного региона. Мне хотелось понять, есть ли что-то похожее на моем железе и, собирая метрики для различных случаев, я случайно наткнулся на проседание в retirement bandwidth при 7-ми микрооперациях. Далее, разобравшись почему так происходит, мне стало интересно будет ли это заментым ограничителем по производительности для, скажем, L1d-bound кода. Я рассмотрел пример, который содержит 7 микроопераций в цикле и померил его. Посколько метрика resource_stall.any была на 4 порядка меньше uops_retired.total_cycles стало ясно, что пропускной способности L1D хватало, чтобы не давать переполниться ROB, MOB(SB+LB) и RS. На графиках видно, что при цикле из 12-микроопераций проседания retirement bandwidth не наблюдается (6 + 6 за 2 такта, при пиковой Renamer bandwidth 4 + 4 за 2 такта), поэтому для устранения этой неэффективности достаточно раскрутить цикл один раз, так что он будет содержать 12 микроопераций.

А вот практический пример не особо понятен.


Для оценки раскидывания микроопераций по линиям можно использовать тот же подход, что и в случае с nop-ами. В данном случае добавляеются операции чтения и записи. Документация Intel описывает, что запись состоит из 2-х микроопераций (Store Address, Store Data), однако к данной инструкции применяется Micro Fusion — т.е. в µop cache они будут храниться как одна микрооперация, а при попадании в RS эта микрооперация будет зашедулена на 2 соответствующих порта. (Т.е. Micro Fusion — это когда одна микрооперация шедулиться несколько раз). Также к cmp-jne применяется Macro Fusion, т.е. две инструкции схлопываются в одну микрооперацию и шедулятся один раз. Таким образом, учитывая micro/macro fusion получаем 7 микроопераций в цикле
Для оценки раскидывания микроопераций по линиям можно использовать тот же подход, что и в случае с nop-ами

Ага, то есть глянули сгенерированные инструкции, прикинули сколько микроперации и заприметили что просадка идёт потому что их 7.

Да, все так.

Только после того, как прикинули нужно убедиться, что нет грубой ошибки в подсчете. Для этого необходимо сравнить ожидаемое количество микроопераций с каунтером uops_retired.retire_slots. В этом конкретном случае имеем (1 << 31) * 7 = 15032385536; uops_retired.retire_slots = 15 188 534 289. uops_executed.thread будет сильно отличаться из-за micro fusion. uops_issued.any получается несколько больше (предположительно это возникает из-за операций с памятью и misspeculate'ом, который вызван memory disambiguation).
Sign up to leave a comment.

Articles