Есть ли узкое место при доступе к «оригинальным» регистрам на i7? - PullRequest
6 голосов
/ 17 марта 2019

Короткая версия:

На Intel i7 есть какие-то узкие места в доступе к «оригинальным» регистрам (eax, ebx, ecx, edx), которых нет в «новых» регистрах (r8d, r9d и т. Д.)? Я синхронизирую некоторый код, и, если я попытаюсь запустить три инструкции добавления параллельно, я могу получить CPI 0,33, при условии, что только две из трех операций добавления ссылаются на «оригинальный» регистр (например, я использую eax, ebx и r9d). Если я попытаюсь использовать три «оригинальных» регистра, индекс потребительских цен увеличится примерно до 0,4. Я наблюдал как на i7-3770, так и на i7-4790.

Подробности:

Я пытаюсь разработать новую (надеюсь, интересную) лабораторию для своего класса по компьютерной архитектуре. Цель состоит в том, чтобы они рассчитали некоторый ассемблерный код на процессоре Intel i7 и наблюдали такие вещи, как (а) пропускная способность процессора и (б) последствия зависимости данных.

Когда я пытаюсь написать какой-то ассемблерный код, который показывает средний CPI 0,33 (т. Е. Демонстрирует, что ЦП может поддерживать пропускную способность 3 инструкции за такт), я обнаружил, что это возможно только в том случае, если не более двух из три инструкции обращаются к «оригинальным» регистрам общего назначения.

Настройка эксперимента

Вот основной план эксперимента: используйте rdtsc для определения временных сегментов нескольких тысяч инструкций, затем выведите «счетчик циклов» в зависимости от количества команд, рассчитанных для оценки пропускной способности. Например, выполнение этого кода внутри цикла

    mov $0, %eax
    cpuid
    rdtsc
    movl    %eax, %r12d
    addl    $1, %eax
    addl    $1, %eax
    # the line above is copied "n" times 
    # (I use a ruby script to generate this part of the assembly)
    addl    $1, %eax

    rdtsc
    subl    %r12d, %eax

позволяет нам сообщить, сколько времени (в ссылочных циклах) требуется для выполнения последовательности n addl инструкций. (Приведенный выше фрагмент кода является частью более длинной программы, которая многократно повторяет измерение, отбрасывает первые несколько тысяч испытаний и сообщает о самом низком и / или наиболее распространенном результате.)

Результаты, которые имеют смысл

Когда я вычисляю последовательность операций добавления в один регистр, я получаю ожидаемый результат:

instructions    elapsed reference ref cycles       estimated actual  actual cycles
between rdtsc   cycles            per instruction  cycles            per instruction
     200           145             0.72                169             0.84
     300           220             0.73                256             0.85
     400           314             0.79                365             0.91
     500           408             0.82                474             0.95
     600           483             0.81                562             0.94
     700           577             0.82                671             0.96
     800           652             0.81                758             0.95
     900           746             0.83                867             0.96
    1000           840             0.84                977             0.98
    1100           915             0.83               1064             0.97
    1200          1009             0.84               1173             0.98
    ........................................................................
    3500          3019             0.86               3510             1.00
    3600          3094             0.86               3598             1.00
    3700          3188             0.86               3707             1.00
    3800          3282             0.86               3816             1.00
    3900          3357             0.86               3903             1.00
    4000          3451             0.86               4013             1.00

После преобразования эталонных циклов в (предполагаемые) фактические циклы процессор усредняет примерно одну инструкцию за цикл. Это имеет смысл, поскольку каждая синхронизированная инструкция зависит от предыдущей инструкции, тем самым предотвращая параллельное выполнение. Обратите внимание, что мы не выдаем команду сериализации до окончания rdtsc. В результате последние несколько дюжин синхронизированных инструкций еще не завершены, когда мы «останавливаем» таймер. Следовательно, ИПЦ для первых нескольких строк этой таблицы является искусственно низким. Эффект этого «недоучета» ограничивается до нуля по мере увеличения количества команд по времени.

Если мы изменим временной код для чередования дополнений к eax и ebx, мы также получим ожидаемый результат: ИПЦ, который стремится к 0,5:

    mov $0, %eax
    cpuid
    rdtsc
    movl    %eax, %r12d

    addl    $1, %eax
    addl    $1, %ebx

    addl    $1, %eax
    addl    $1, %ebx

    # the pair of lines above are copied until there are `n` lines total being timed

    addl    $1, %eax
    addl    $1, %ebx

    rdtsc
    subl    %r12d, %eax

instructions    elapsed reference ref cycles       estimated actual  actual cycles
between rdtsc   cycles            per instruction  cycles            per instruction
    1000           432             0.43                502             0.50
    1200           510             0.42                593             0.49
    1400           601             0.43                699             0.50
    1600           695             0.43                808             0.51
    1800           773             0.43                899             0.50
    2000           864             0.43               1005             0.50
    2200           955             0.43               1110             0.50

Вопрос: Почему имеет значение, какой регистр я использую при попытке запустить 3 инструкции параллельно?

Когда я пытаюсь запустить добавление к eax, ebx и ecx параллельно, ИПЦ выше ожидаемого .33:

    mov $0, %eax
    cpuid
    rdtsc
    movl    %eax, %r12d

   addl    $1, %eax
   addl    $1, %ebx
   addl    $1, %ecx

   addl    $1, %eax
   addl    $1, %ebx
   addl    $1, %ecx

   # the group of lines above are copied until there are `n` lines total being timed

   addl    $1, %eax
   addl    $1, %ebx
   addl    $1, %ecx

   rdtsc
   subl    %r12d, %eax


instructions    elapsed reference ref cycles       estimated actual  actual cycles
between rdtsc   cycles            per instruction  cycles            per instruction
    1200           408             0.34                474             0.40
    1500           492             0.33                572             0.38
    1800           595             0.33                692             0.38
    2100           698             0.33                812             0.39
    2400           782             0.33                909             0.38
    2700           885             0.33               1029             0.38
    3000           988             0.33               1149             0.38
    3300          1091             0.33               1269             0.38
    3600          1178             0.33               1370             0.38

Однако я получаю ожидаемый результат, если использую r9d, r10d и r11d:

instructions    elapsed reference ref cycles       estimated actual  actual cycles
between rdtsc   cycles            per instruction  cycles            per instruction
    1200           350             0.29                407             0.34
    1500           444             0.30                516             0.34
    1800           519             0.29                603             0.34
    2100           613             0.29                713             0.34
    2400           707             0.29                822             0.34
    2700           782             0.29                909             0.34
    3000           876             0.29               1019             0.34

Фактически, я получаю ожидаемый результат, если максимум два из трех регистров поступают из набора eax, ebx, ecx и edx. Это почему? Любая идея, является ли узкое место в проблеме, декодировании, переименовании регистра или выходе на пенсию?

Я наблюдал такое поведение на i7-3770 и i7-4790. Для чего это стоит: у Ryzen 7 и i5-6500 всегда есть ИПЦ от 0,38 до 0,40, независимо от используемых регистров.


код

Для тех, кому интересно, вот шаблон для кода, который я использую:

.file   "timestamp_shell.c"
    .text
    .section    .rodata
    .align 8
.LC0:
    .string "%8d; Start %10u; Stop %10u; Difference %5d\n"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    pushq   %r13
    pushq   %r12
    pushq   %rbx
    subq    $8, %rsp
    .cfi_offset 13, -24
    .cfi_offset 12, -32
    .cfi_offset 3, -40
    movl    $100, %r12d
    movl    $200, %r13d
    movl    $-1, %r8d
    movl    $0, %r8d
    jmp .L2
    .L3:
    mov $0, %eax
    cpuid
    rdtsc
    movl    %eax, %r12d
    movl $0, %eax

    # I use a perl script to copy the lines marked with #@ until there
    # is the desired number of instructions between the calls to rdstc

    #@    addl $1, %eax
    #@    addl $1, %r10d
    #@    addl $1, %ecx

    rdtsc
    subl    %r12d, %eax
    movl    %eax, %r8d
    movl    %r13d, %ecx
    movl    %r12d, %edx
    movl    %r8d, %esi
    leaq    .LC0(%rip), %rdi
    movl    $0, %eax
    call    printf@PLT
    addl    $1, %r8d
.L2:
    cmpl    $999999, %r8d
    jle .L3
    movl    $199, %eax
    addq    $8, %rsp
    popq    %rbx
    popq    %r12
    popq    %r13
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 8.2.1 20181127"
    .section    .note.GNU-stack,"",@progbits
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...