Почему jnz не считает цикл? - PullRequest
0 голосов
/ 04 января 2019

Я обнаружил на онлайн-ресурсе, что IvyBridge имеет 3 ALU.Поэтому я пишу небольшую программу для тестирования:

global _start
_start:
    mov rcx,    10000000
.for_loop:              ; do {
    inc rax
    inc rbx
    dec rcx
    jnz .for_loop       ; } while (--rcx)

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

Я компилирую и запускаю ее с perf:

$ nasm -felf64 cycle.asm && ld cycle.o && sudo perf stat ./a.out

Вывод показывает:

10,491,664      cycles

на первый взгляд, это имеет смысл, потому что есть 3 независимых инструкции (2 inc и 1 dec), которые используют ALU в цикле, поэтому они считают 1 цикл вместе.

Но я не понимаю, почему весь цикл имеет только 1 цикл?jnz зависит от результата dec rcx, он должен считать 1 цикл, так что весь цикл равен 2 циклам.Я ожидаю, что выходной сигнал будет близок к 20,000,000 cycles.

Я также попытался изменить второе inc с inc rbx на inc rax, что делает его зависимым от первого inc.Результат становится близким к 20,000,000 cycles, что показывает, что зависимость задержит выполнение инструкции, поэтому они не могут выполняться одновременно.Так почему jnz особенный?

Чего мне здесь не хватает?

1 Ответ

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

Прежде всего, dec/jnz слится в одном макросе в семействе Intel Sandybridge.Вы можете победить это, поместив инструкцию без установки флага между dec и jnz.

.for_loop:              ; do {
    inc rax
    dec rcx
    lea rbx, [rbx+1]    ; doesn't touch flags, defeats macro-fusion
    jnz .for_loop       ; } while (--rcx)

Это все равно будет выполняться по 1 итеру за цикл в Haswell и более поздних версиях и в Ryzen, поскольку они имеют 4 целочисленных порта выполнениячтобы не отставать от 4 мопов за итерацию.(Ваш цикл с макро-слиянием - это всего 3 мопа с плавким доменом на процессорах Intel, поэтому SnB / IvB также может запускать его по 1 за такт.)

См. Руководство по оптимизации Agner Fog *.и особенно его гид по микроарху.Также другие ссылки в https://stackoverflow.com/tags/x86/info.


Управляющие зависимости скрыты предсказанием ветвлений + спекулятивным выполнением, в отличие от зависимостей данных.

Внеочередное выполнение ипредсказание ветвлений + умозрительное выполнение скрывают «задержку» управляющей зависимости.то есть следующая итерация может начаться до того, как процессор подтвердит, что jnz действительно должен быть взят.

Таким образом, каждый jnz имеет входную зависимость от предыдущего dec rcx, прежде чем он сможет проверить прогноз, но позжеинструкции не должны ждать, пока они будут проверены, прежде чем они смогут выполнить. Выход на пенсию гарантирует, что ложная спекуляция обнаруживается, прежде чем что-либо может "увидеть", что это происходит (за исключением микроархитектурных эффектов, ведущих к атаке Призрака ...)


10Mитераций не много.Я обычно использовал бы по крайней мере 100M для чего-то, что работает только в 1c на iter.Простой прогон микробенчмарка в течение 0,1-1 секунды обычно хорош для получения очень высокой точности и скрытия накладных расходов при запуске.

И кстати, вам не нужно sudo perf, если вы установите kernel.perf_event_paranoid = 0 с помощью sysctl.Это почти наверняка лучше сделать, чем использовать sudo все время.

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