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 циклов), но из того, что я понимаю в конвейерной обработке, безусловные переходы должны быть очень быстрыми (без ошибок предсказания ветвлений).

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

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 циклов на джамп устойчивую пропускную способность очень близки к вашим результатам (которые, вероятно, были из другой микроархитектуры).

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

Другие вопросы по тегам