B и A образуют цепочку зависимостей, переносимых циклом . A в следующей итерации не может быть запущено, пока не получится результат B в предыдущей.
A B никогда не может работать в том же цикле, что и A : какой вход будет использовать более поздний, если предыдущий еще не дал результата
Эта цепочка имеет длину 2 цикла (на одну итерацию), потому что задержка inc
равна 1 циклу. Это создает узкое место задержки в бэкэнде, которое не может скрыть выполнение вне очереди. (За исключением очень низкого числа итераций, где он может перекрывать его с кодом после цикла).
Так же, как если бы вы полностью развернули огромную цепочку times 102400 inc eax
, для процессора не существует параллелизма на уровне команд, который можно было бы найти между цепочкой команд, каждая из которых зависит от предыдущей.
Макрос слияния dec rcx/jnz
uop не зависит от цепочки RAX и является более короткой цепочкой (всего 1 цикл на итерацию, только 1 дек и ветвление с задержкой 1c). Так что он может работать параллельно с B или A мопс.
См. мой ответ на другой вопрос для получения дополнительной информации о концепции параллелизма на уровне команд и цепочек зависимостей, а также о том, как ЦП используют этот параллелизм для параллельного выполнения инструкций , когда они независимы .
PDF-файл Agner Fog microarch показывает это с примерами из ранней главы: Глава 2: Выполнение не по порядку (Все процессоры, кроме P1,
PMMX) .
Если вы запускаете новую 2-тактную цепочку депиляции на каждой итерации, она будет работать так, как вы ожидаете . Новая цепочка, отбрасывающая каждую итерацию, открыла бы параллелизм на уровне команд для ЦП, чтобы одновременно удерживать A и B от разных итераций в полете.
.for_loop:
xor eax,eax ; dependency-breaking for RAX
inc rax ; uop A
inc rax ; uop B
dec rcx ; uop C
jnz .for_loop
Семейство Sandybridge обрабатывает обнуление xor без исполнительного модуля, так что это все еще только 3 мопа unused-domain в цикле, поэтому IvyBridge имеет достаточно исполнительных портов ALU для запуска всех 3 в одном цикле. Это также максимизирует входную частоту до 4 мопов слитых доменов за такт.
Или, если вы изменили A, чтобы запустить новую цепочку депонирования в RAX с любой инструкцией, которая безоговорочно перезаписывает RAX без зависимости от результата inc
, все будет в порядке.
lea rax, [rdx + rdx] ; no dependency on B from last iter
inc rax ; uop B
За исключением пары инструкций с неудачной выходной зависимостью: Почему нарушение "выходной зависимости" LZCNT имеет значение?
popcnt rax, rdx ; false dependency on RAX, 3 cycle latency
inc rax ; uop B
В процессорах Intel только popcnt
и lzcnt/tzcnt
имеют выходные зависимости без причины. Это потому, что они используют тот же исполнительный модуль, что и bsf
/ bsr
, что оставляет неизменным назначение, если ввод равен нулю, на процессорах Intel и AMD. Intel все еще документирует это на бумаге как неопределенное, если ввод равен нулю для BSF / BSR , но они создают аппаратное обеспечение, которое реализует более строгие гарантии. (AMD даже документирует это поведение BSF / BSR.) Так или иначе, поэтому BSF / BSR Intel похожи на CMOV и нуждаются в качестве места назначения в качестве входа в случае, если для параметра reg установлено значение 0. popcnt
, (и lzcnt / tzcnt на предварительном этапе. Скайлэйк) от этого тоже страдают.
Если вы сделали цикл более 5 мопов с слитыми доменами, SnB / IvB мог бы выдавать его в лучшем случае 1 на 2 цикла от внешнего интерфейса. Haswell и позже «развернуть» в буфере цикла или что-то еще, так что цикл 5 моп может работать при ~ 1,25 с за одну итерацию, но SnB / IvB нет. Снижается ли производительность при выполнении циклов, число операций которых не кратно ширине процессора?
Начиная с Core 2, этап выпуска / переименования переднего плана имеет ширину 4 мопа с плавким доменом в процессорах Intel.