Могут ли современные процессоры x86 идеально выполнять внеочередное выполнение?
Моя мысленная модель выполнения вне очереди состоит в том, чтобы думать об этом как о скользящем окне в потоке команд, где, если в окне есть какие-либо готовые инструкции (их входные данные уже вычислены), их можно запустить немедленно, даже если перед ними в потоке есть другие инструкции, если доступны ресурсы ЦП.
Однако я пытаюсь понять влияние упорядочивания инструкций, которые не зависят друг от друга. Скажем, у меня есть два набора по три инструкции, каждый из которых образует независимые цепочки зависимостей.A,B,C
иX,Y,Z
. Ни одна из этих инструкций не меняет поток управления. Поскольку инструкции в каждом наборе образуют цепочку зависимостей, инструкции внутри набора не могут выполняться не по порядку (C,B,A
недопустимо), но поскольку цепочки независимы, их можно чередовать друг с другом (A,X,B,Y,C,Z
действует). Далее предположим, что все шесть инструкций помещаются внутри скользящего окна и находятся в кеше. Будут ли все допустимые типы шести инструкций иметь одинаковую производительность или некоторые из них будут быстрее других ? С N цепочками из M инструкций?
Я подозреваю, что нет, потому что кажется, что идеальное планирование было бы сложно реализовать на аппаратном уровне, но, может быть, существуют достаточно хорошие на практике эвристики, используемые реальными процессорами?
Предполагая, что некоторые виды сортировки выполняются быстрее других, как с помощью счетчиков производительности или других инструментов можно обнаружить, что ваш код работает медленнее, чем мог бы, из-за плохой сортировки?Есть ли способ заметить, что сортировка в сборке для горячего цикла плоха? В отличие от более обычных подозреваемых, таких как неверные прогнозы ветвей и промахи в кэше.
1 ответ
Концепция, о которой вы спрашиваете, - это «планирование», либо статическое с помощью компилятора (порядок программы asm), либо динамически в процессоре (с помощью «планировщика», также известного как RS = Станции резервирования).
См. Как именно планируются операции x86?- для каждого порта выполнения, сначала самый старый , не определяя, какие мопы являются частью длинной цепочки зависимостей критического пути. Обычно это происходит естественным образом после нескольких итераций цикла, поскольку невыполненные операции являются частью цепочки зависимостей, переносимой циклом.
Для чего-то вроде целого числаa*b*c + d*e
, вы хотели бы запланироватьa*b
before , так как это часть цепочки из 2 с (задержка 3 цикла, пропускная способность 1/цикл). Еслиd*e
статически первым, в порядке программы (и все 5 входов в регистрах готовы), он будет выполняться первым. Таким образом, более длинная цепочка зависимостей не может начаться до следующего цикла, что приводит к длине критического пути1(resource conflict) + 3+3(imuls) + 1(add)
. Если задержка критического пути всего этого выражения является частью более длинной цепочки dep, которая является общим узким местом (однако каким-то образом у нас все еще были готовы все 5 входов одновременно или, по крайней мере, d, e, a и b), то статическое планирование критический путь uops в первую очередь поможет избежать конфликта ресурсов для одного порта выполнения, имеющего целочисленный множитель.
(Я выбрал целочисленное умножение для более простого примера, потому что современные процессоры x86 по-прежнему имеют только один исполнительный порт, который может его обработать. Перетасовка SIMD на процессорах Intel — еще один распространенный пример этого, по крайней мере, для некоторых перетасовок, которые не могут работать на порте Ice Lake. 1 или с шириной вектора 512 бит.)
uops назначаются портам во время выпуска/переименования/распределения (когда они перемещаются из упорядоченного внешнего интерфейса в неупорядоченный внутренний интерфейс, RS и ROB = ReOrder Buffer). Это делается параллельно для всех операций в группе задач (например, от 4 до 6 на последней версии x86) на основе количества невыполненных операций для каждого порта в начале цикла. Планирование может быть неоптимальным на первых двух итерациях цикла; см. инструкцию x86_64, запланированную для уже используемого порта вместо неиспользуемого - Intel использует некоторую эвристику тай-брейка для распределения операций по портам, когда длина очереди близка, не проверяя ограничения портов между операциями в одном и том же цикле.
Блок-схемы процессоров см.
- https://www.realworldtech.com/sandy-bridge/5/
- https://chipsandcheese.com/2022/11/05/amds-zen-4-part-1-frontend-and-execution-engine/ — Zen во многом похож на Intel, за исключением отдельных планировщиков и портов для целочисленных и целочисленных вычислений. ФП. (Очевидно, планировщик Intel больше не полностью унифицирован в современных конструкциях: некоторые записи могут содержать только некоторые типы операций.)
- https://agner.org/optimize/ — никаких диаграмм, но хорошие подробности о различных микроархитектурах.
как с помощью счетчиков производительности или других инструментов можно обнаружить, что ваш код работает медленнее, чем мог бы, из-за плохой сортировки?
В общем, сложно обнаружить. Если вы подсчитали ожидаемое узкое место задержки в цепочке зависимостей с циклическим переносом, но на самом деле она работает медленнее, и нет зависаний из-за промахов в кэше или внешнего интерфейса, тогда стоит обратить внимание на планирование uop. .
На процессорах Intelidq_uops_not_delivered.cycles_fe_was_ok
подсчитывает циклы, в которых были мопы, готовые к выпуску из внешнего интерфейса (IDQ = очередь декодирования инструкций), но этого не произошло из-за зависаний внутреннего интерфейса.
Развертывание для сокрытия задержки (путем перекрытия нескольких цепочек выполнения вместо одной более длинной) чрезвычайно важно для операций с более высокой задержкой, таких как математические вычисления с плавающей запятой, скалярное произведение или суммирование массивов.
- Почему на Haswell mulss занимает всего 3 цикла, в отличие от таблиц инструкций Агнера? (Развертывание циклов FP с несколькими аккумуляторами) – развертывание помогает выйти за рамки минимума, который может потребоваться при идеальном планировании. При наличии всего 8 цепочек операций и двух 4-тактных конвейеров FMA в Skylake любое несовершенное планирование может привести к циклу, в котором ни один FMA не выполняется на одном из каналов, что приводит к потере пропускной способности, а также к потере прогресса задержки, которую невозможно компенсировать. (Поскольку окно выхода из строя ограничено размером RS, его заполнение также может в конечном итоге привести к потере цикла выполнения других цепочек выполнения.)
- Являются ли операции загрузки и сохранения единственными переупорядочиваемыми инструкциями?- микробенчмарк-эксперимент с двумя длинными
imul
dep (задержка 3c, пропускная способность 1c), насколько полно они могут перекрываться. - Понимание влияния lfence на цикл с двумя длинными цепочками зависимостей при увеличении длины — подробнее об этом эксперименте.