x86-64 Относительная производительность jmp

В настоящее время я выполняю задание, которое измеряет производительность различных команд x86-64 (синтаксис at&t).

Команда, с которой я несколько смущен, - это команда «безусловный jmp». Вот как я это реализовал:

    .global uncond
uncond:

.rept 10000
jmp . + 2
.endr


mov $10000, %rax
ret

Это довольно просто. Код создает функцию под названием «uncond», которая использует директиву .rept для вызова команды jmp 10000 раз, а затем устанавливает возвращаемое значение, равное количеству вызовов команды jmp.

"." в синтаксисе at&t означает текущий адрес, который я увеличиваю на 2 байта, чтобы учесть саму инструкцию jmp (поэтому jmp . + 2 должен просто перейти к следующей инструкции).

Код, который я не показал, вычисляет количество циклов, необходимых для обработки 10000 команд.

Мои результаты говорят, что jmp довольно медленный (для обработки одной инструкции jmp требуется 10 циклов), но из того, что я понимаю в конвейерной обработке, безусловные переходы должны быть очень быстрыми (без ошибок предсказания ветвления).

Я что-то упускаю? Мой код неправильный?


person acz    schedule 24.04.2016    source источник
comment
Возможный дубликат Slow jmp-instruction. На этот более подробный вопрос есть гораздо лучший и более подробный ответ.   -  person Peter Cordes    schedule 15.08.2016


Ответы (1)


ЦП не оптимизирован для неоперабельных jmp инструкций, поэтому он не обрабатывает особый случай продолжения декодирования и конвейерной обработки инструкций jmp, которые просто переходят к следующему insn.

Однако процессоры оптимизированы для циклов. jmp . будет выполняться с частотой один insn за такт на многих ЦП или один за 2 такта на некоторых ЦП.


Прыжок создает пузырек при получении инструкций. Один хорошо спрогнозированный прыжок — это нормально, но запустить только прыжки проблематично. Я воспроизвел ваши результаты на core2 E6600 (микроархитектура Merom/Conroe):

# jmp-test.S
.globl _start
_start:

    mov $100000, %ecx
jmp_test:
    .rept 10000
    jmp . + 2
    .endr

    dec %ecx
    jg jmp_test


    mov $231, %eax
    xor %ebx,%ebx
    syscall          #  exit_group(0)

построить и запустить с помощью:

gcc -static -nostartfiles jmp-test.S
perf stat -e task-clock,cycles,instructions,branches,branch-misses ./a.out

 Performance counter stats for './a.out':

       3318.616490      task-clock (msec)         #    0.997 CPUs utilized          
     7,940,389,811      cycles                    #    2.393 GHz                      (49.94%)
     1,012,387,163      instructions              #    0.13  insns per cycle          (74.95%)
     1,001,156,075      branches                  #  301.679 M/sec                    (75.06%)
           151,609      branch-misses             #    0.02% of all branches          (75.08%)

       3.329916991 seconds time elapsed

Из другого запуска:

 7,886,461,952      L1-icache-loads           # 2377.687 M/sec                    (74.95%)
     7,715,854      L1-icache-load-misses     #    2.326 M/sec                    (50.08%)
 1,012,038,376      iTLB-loads                #  305.119 M/sec                    (75.06%)
           240      iTLB-load-misses          #    0.00% of all iTLB cache hits   (75.02%)

(Числа в (%) в конце каждой строки показывают, сколько всего времени работы счетчик был активен: perf должен выполнять мультиплексирование для вас, когда вы просите его подсчитать больше вещей, чем HW может подсчитать за один раз).

Так что на самом деле это не промахи I-кэша, это просто узкие места во внешнем интерфейсе при выборке/декодировании инструкций, вызванные постоянными скачками.

Моя машина SnB сломана, поэтому я не могу проверить цифры на ней, но 8 циклов на устойчивую пропускную способность jmp довольно близки к вашим результатам (которые, вероятно, были из другой микроархитектуры).

Для получения дополнительной информации см. http://agner.org/optimize/ и другие ссылки из x86 пометить вики.

person Peter Cordes    schedule 24.04.2016