Может ли процессор выполнять операции памяти и арифметики одновременно?

При изучении ассемблера и процессора меня уводит одно, как делается инструкция:

add mem, 1

В моей голове процессор не может загрузить значение памяти и обработать арифметическую операцию во время одной и той же инструкции. Так что я думаю, что это происходит так:

mov reg, mem
add reg, 1
mov mem, reg

Если я рассмотрю процессор с RISC Pipeline, мы можем наблюдать некоторые задержки. Это удивительно для такой простой инструкции, как i++:

|  Fetch  | Decode  | Exec    | Memory  | WriteB  |
          |  Fetch  |         |         | Decode  | Exec    | Memory  | WriteB  |
                    |  Fetch  |         |         |         | Decode  | Exec    | Memory  | WriteB  |

(Как я мог прочесть в книге Паттерсона " Архитектура компьютера: количественный подход", регистры считываются в Decode uOp, Store / Load in Memory uOp, и мы позволяем себе принимать значение регистра в Memory uOp.)

Я прав? или у современных процессоров есть конкретные методы, чтобы сделать это более эффективно?

1 ответ

Решение

Вы правы, современный x86 будет декодировать add dword [mem], 1 до 3 моп: загрузка, добавление ALU и хранилище.

Эти 3 зависимых операции не могут происходить одновременно, потому что последним нужно ждать результата предыдущего.

Но выполнение независимых инструкций может перекрываться, и современные процессоры очень настойчиво ищут и используют "параллелизм на уровне команд", чтобы запускать ваш код быстрее, чем 1 моп за такт. Посмотрите этот ответ для введения в то, что одно ядро ​​ЦП может делать параллельно, со ссылками на другие материалы, такие как руководство по микроархиву x86 от Agner Fog, и рецензии Дэвида Кантера на Sandybridge и Bulldozer.


Но если вы посмотрите на семейства микроархитектур P6 и Sandybridge от Intel, то магазин - это на самом деле отдельный адрес магазина и данные магазина. Uop-адрес хранилища не зависит от загрузки или ALU и может записать адрес хранилища в буфер хранилища в любое время. (Руководство по оптимизации Intel называет это буфером порядка памяти).

Чтобы увеличить пропускную способность внешнего интерфейса, входные данные store-address и store-data могут быть декодированы как пара с микроплавлением. За add Таким образом, можно выполнить операцию load+alu, чтобы процессор Intel мог декодировать add dword [rdi], 1 до 2 мопов слитых доменов. (Та же нагрузка + добавление микро-фьюжн работает для декодирования add eax, [rdi] к одному мопу, так что любой из "простых" декодеров может декодировать его, а не только "сложный" декодер, который может обрабатывать многопользовательские инструкции. Это уменьшает входные узкие места).

Вот почему add [mem], 1 более эффективен, чем inc [mem] на процессорах Intel, хотя inc reg так же эффективно (но меньше), чем add reg,1, (inc не может микросоплавить его load+inc, который устанавливает флаги иначе, чем add). Инструкция INC против ADD 1: имеет ли это значение?

Но это только помогает внешнему интерфейсу быстрее вводить мопы в планировщик; нагрузка по-прежнему должна выполняться отдельно от надстройки.

Но нагрузка с микроплавлением не должна ждать, пока все остальные входные данные инструкции будут готовы. Рассмотрим инструкцию как add [rdi], eax где RDI и EAX являются входными данными для инструкции, но EAX не требуется, пока ALU не добавит uop. Загрузка может быть выполнена, как только загрузочный адрес будет готов, и есть свободный модуль выполнения загрузки (AGU + доступ к кешу). Смотрите также Как запланировано выполнение x86-мопов?,


регистры считываются в Decode uOp, Store/Load in Memory uOp, и мы позволяем себе принимать значение регистра в Memory uOp

Все текущие микроархитектуры x86 используют неупорядоченное выполнение с переименованием регистров (алгоритм Томасуло). Инструкции переименовываются и выдаются в вышедшую из строя часть ядра (ROB и планировщик).

Физический регистровый файл не читается, пока инструкция не "отправлена" из планировщика в исполнительный модуль. (Или для недавно сгенерированных входных данных, переадресованных из других мопов.)


Независимые инструкции могут перекрывать их выполнение. Например, процессор Skylake может поддерживать пропускную способность 4 мопов в слитых доменах / 7 мопов в неиспользуемых доменах за такт, включая 2 загрузки + 1 хранилище, в тщательно продуманном цикле:

.loop: ; HSW: 1.12c / iter. SKL: 1.0001c
    add edx, [rsp]           ; 1 fused-domain uop:  micro-fused load+add
    mov [rax], edi           : 1 fused-domain uop:  micro-fused store-address+store-data
    blsi ebx, [rdi]          : 1 fused-domain uop:  micro-fused load+bit-manip

    dec ecx
    jnz .loop                ; 1 fused-domain uop: macro-fused dec+branch runs on port 6

Процессоры семейства Sandybridge имеют кэш L1d, способный на 2 чтения + 1 запись в такт. (До Haswell только 256-битные векторы могли работать вокруг предела пропускной способности AGU. См. Как кэширование может быть таким быстрым?)

Пропускная способность фронт-энда семейства Sandybridge составляет 4 мопа слитых доменов за такт, и у них есть множество исполнительных блоков в бэк-энде для обработки различных комбинаций команд. (Haswell и более поздние версии имеют 4 целочисленных ALU, 2 порта загрузки, порт хранилища данных и выделенный AGU хранилища для простых режимов адресации хранилища. Таким образом, они часто могут быстро "догнать" после выполнения остановки кэша, быстро делая комната в нерабочем окне, чтобы найти больше работы.)

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