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

Итак, компилятор может свободно переупорядочивать фрагменты кода из соображений производительности. Предположим, что какой-то фрагмент кода, переведенный непосредственно в машинный код без применения оптимизаций, выглядит так:

machine_instruction_1
machine_instruction_2
machine_instruction_3
machine_instruction_4
machine_instruction_5

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

machine_instruction_5
machine_instruction_4
machine_instruction_3
machine_instruction_2
machine_instruction_1

Все идет нормально.

Вот где начинается сложная часть. Результирующие машинные инструкции будут выполняться процессором, который может снова перетасовать их любым способом, который он сочтет подходящим для соображений производительности, до тех пор, пока сохраняется логика кода. Поскольку мы имеем дело с двумя «слоями» переупорядочения инструкций:

  • первый из-за оптимизации компилятора
  • второй, из-за внеочередного выполнения процессора

что вообще делает переупорядочивание инструкций во время компиляции актуальным? Все, что видит процессор, — это последовательность необработанных машинных инструкций без каких-либо указаний на какие-либо предшествующие оптимизации, выполненные компилятором. Если процессор вводит свой собственный «уровень» переупорядочения, почему он не делает недействительным порядок инструкций, установленный компилятором? По сути, что заставляет процессор соблюдать оптимизацию компилятора? Как переупорядочение во время компиляции и переупорядочение во время выполнения «взаимодействуют», как последнее дополняет первое?


person Peter    schedule 17.01.2019    source источник
comment
Процессор не переупорядочивает инструкции, он выполняет их не по порядку. Это означает, что он может запустить более позднюю инструкцию до того, как завершится более ранняя (точное значение терминов немного техническое). Однако порядок, в котором ЦП сталкивается с инструкциями, по-прежнему актуален, поскольку более ранние инструкции первыми занимают ресурсы (например, если mi1 требует ресурса A на 5 циклов, а mi2 требует A на 1 цикл и B на 4 цикла, планирование mi1+mi2 будет потребуется 10 циклов, в то время как mi2 + mi1 потребует 6 циклов).   -  person Margaret Bloom    schedule 18.01.2019
comment
@MargaretBloom Но разве это не одно и то же? Предположим, исходный порядок — инструкция1, инструкция2, а после оптимизации компилятора новый порядок становится инструкция2, инструкция1. Теперь, даже если процессор встречает инструкцию 2 перед инструкцией 1, он все равно может выполнять их не по порядку, например. выполнить инструкцию1 перед инструкцией2, таким образом эффективно игнорируя оптимизацию компилятора. Возможно, я не понимаю разницы между тем, как компилятор перетасовывает инструкции, и тем, как процессор выполняет их не по порядку. Небольшой пример определенно поможет.   -  person Peter    schedule 13.02.2019


Ответы (1)


При рассмотрении выполнения инструкций следует учитывать семантику программы. Любой порядок правилен, если он соблюдается. Конкретно это описывается «зависимостями», которые указывают, требуют ли некоторые инструкции заданного порядка в отношении правильного поведения программы. Например, рассмотрим следующую программу

1 x <= y+3
2 z <= 2*x
3 w = 5*y
4 y = 2*a

Инструкции 1 и 2 являются зависимыми. Если их относительный порядок изменен, программа не соответствует тому, что хотел программист, и любое изменение порядка запрещено. По разным причинам 4 не может быть выполнен без изменений до тех пор, пока y не будет использован 1 и 3. Существуют различные виды зависимостей, в том числе при рассмотрении потока управления.

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

Компилятор может рассматривать более крупные реорганизации, чем процессор, и использует для этого более сложные эвристики. Компилятор может иметь широкое представление о программе и переупорядочивать большую часть кода. Теоретически инструкция на расстоянии, скажем, 1000 может быть смещена, если нет нарушения зависимости и компилятор считает, что это может улучшить выполнение программы. Он может полностью реорганизовать код, разворачивать циклы и т. д. Напротив, процессор имеет относительно ограниченное окно предварительно выбранных инструкций, которые можно рассматривать для переупорядочивания, и любая перестановка может касаться только близких инструкций и основана на простых методах, выполнимых в рамках цикла.

Но у процессора есть большое преимущество. Он может выполнять динамическое изменение порядка и реагировать на случайные события. В данный момент он рассматривает, какая инструкция может быть выполнена с учетом зависимостей и доступности данных, и соответствующим образом переупорядочивает код. Это основано на динамических зависимостях, и, например, если результат предыдущей зависимой инструкции недоступен из-за промаха кеша, он выполнит другие инструкции, учитывающие зависимости. Последовательные запуски одной и той же программы с одними и теми же входными данными могут привести к различному порядку в зависимости от неверных предсказаний ветвлений, промахов кэша и т. д.

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

person Alain Merigot    schedule 17.01.2019
comment
Также ЦП может оптимистично выполнять вычисления заранее, а затем удалять их, если они становятся недействительными из-за неожиданной зависимости. Но компилятор, который попытается это сделать, может сильно пессимизировать код, поскольку он не знает, когда вычислительные единицы становятся доступными для точной модели, используемой для запуска кода (если только не скомпилирован с точной абстрактной моделью этого ЦП, если такая модель вообще доступна). и не является коммерческой тайной). - person curiousguy; 21.01.2020