Почему зависимость в итерации цикла не может быть выполнена вместе с предыдущей - PullRequest
0 голосов
/ 05 января 2019

Я использую этот код для проверки влияния зависимости в итерации цикла на IvyBridge:

global _start
_start:
    mov rcx,    1000000000
.for_loop:          
    inc rax     ; uop A
    inc rax     ; uop B
    dec rcx     ; uop C
    jnz .for_loop   

    xor rdi,    rdi
    mov rax,    60  ; _exit(0)
    syscall

Поскольку dec и jnz будут слиты в макросе с одним мопом, в моем цикле 3 мопа, они помечены в комментариях.

uop B зависит от uop A, поэтому я думаю, что выполнение будет таким:

A C
B A C  ; the previous B and current A can be in the same cycle
B A C
...
B A C
B

Следовательно, цикл может выполняться 1 цикл на итерацию.

Однако инструмент perf показывает:

 2,009,704,779      cycles                
 1,008,054,984      stalled-cycles-frontend   #   50.16% frontend cycles idl

Так что это 2 цикла на итерацию, и 50% простоя цикла фронтэнда.

Что вызвало 50% простоя фронтэнда? Почему гипотетическая диаграмма выполнения не может быть реализована?

1 Ответ

0 голосов
/ 05 января 2019

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...