Инструкции по переупорядочению процессора x86?
Я читал, что некоторые процессоры переупорядочивают инструкции, но это не проблема для однопоточных программ (инструкции все равно будут переупорядочены в однопоточных программах, но может показаться, что инструкции выполняются по порядку), это только проблема для многопоточных программ.
Чтобы решить проблему переупорядочения инструкций, мы можем вставить барьеры памяти в соответствующие места в коде.
Но инструкции по переупорядочению процессора x86? Если это не так, то нет необходимости использовать барьеры памяти, верно?
1 ответ
Изменение порядка
Да, все современные чипы x86 от Intel и AMD настойчиво переупорядочивают инструкции в окне, которое имеет глубину около 200 команд для последних процессоров обоих производителей (т.е. может выполняться новая инструкция, в то время как более старая инструкция, содержащая более 200 инструкций "в прошлом", все еще остается в силе). ожидания). Как правило, все это невидимо для одного потока, поскольку ЦП по-прежнему поддерживает иллюзию последовательного выполнения 1 текущим потоком, уважая зависимости, поэтому с точки зрения текущего потока выполнения все как если бы команды выполнялись последовательно.,
Барьеры памяти
Это должно ответить на главный вопрос, но тогда ваш второй вопрос о барьерах памяти. Однако в нем содержится неверное предположение о том, что переупорядочение команд обязательно вызывает (и является единственной причиной) видимое переупорядочение памяти. На самом деле, переупорядочение команд не является ни достаточным, ни необходимым для переупорядочения памяти между потоками.
Теперь, безусловно, верно, что неупорядоченное выполнение является основным драйвером возможностей доступа к неупорядоченной памяти, или, возможно, это квест MLP (параллелизм на уровне памяти), который обеспечивает все более мощные неупорядоченные способности для современных процессоров. На самом деле, оба, вероятно, верны сразу: растущие возможности выхода из строя много выигрывают от сильных возможностей переупорядочения памяти, и в то же время агрессивное переупорядочение и перекрытие памяти невозможно без хороших возможностей выхода из строя, поэтому они помогают друг другу в виде самоусиливающейся петли типа "сумма больше чем часть".
Так что да, выполнение не по порядку и переупорядочение памяти, безусловно, имеют отношения; однако, вы можете легко получить повторный заказ без выполнения заказа! Например, буфер основного локального хранилища часто вызывает явное переупорядочение: в момент выполнения хранилище не записывается непосредственно в кэш (и, следовательно, не отображается в точке когерентности), что задерживает локальные хранилища по отношению к локальным загружает, которые должны прочитать их значения в точке выполнения.
Как Питер также указывает в ветке комментариев, вы также можете получить тип переупорядочения нагрузки-нагрузки, когда нагрузкам разрешено перекрываться в упорядоченном проекте: нагрузка 1 может начаться, но при отсутствии инструкции, потребляющей свой результат, конвейерная передача в При проектировании заказа можно следовать следующим инструкциям, которые могут включать в себя другую загрузку 2. Если нагрузка 2 является попаданием в кэш, а загрузка 1 была пропущена в кеше, нагрузка 2 может быть выполнена раньше по времени от нагрузки 1, и, следовательно, очевидный порядок может быть заменен переупорядочен.
Итак, мы видим, что не все переупорядочение памяти между потоками вызвано переупорядочением команд, но определенное переупорядочение команд также подразумевает доступ к памяти вне порядка, верно? Нет так быстро! Здесь есть два различных контекста: что происходит на аппаратном уровне (то есть, могут ли инструкции доступа к памяти, на практике, выполняться не по порядку), и что гарантируется документацией ISA и платформы (часто называемой памятью). модель применима к аппаратному обеспечению).
x86 переупорядочение
Например, в случае x86 современные микросхемы будут свободно переупорядочивать более или менее любой поток загрузок и сохранять их относительно друг друга: если загрузка или хранилище готовы к выполнению, ЦП, как правило, пытается это сделать, несмотря на наличие ранее незавершенных операций загрузки и хранения.
В то же время, x86 определяет довольно строгую модель памяти, которая запрещает большинство возможных переупорядочений, в общих чертах, следующим образом:
- Хранилища имеют единый глобальный порядок видимости, наблюдаемый последовательно всеми процессорами, при условии одного ослабления этого правила ниже.
- Локальные операции загрузки никогда не переупорядочиваются относительно других локальных операций загрузки.
- Операции локального хранилища никогда не переупорядочиваются по отношению к другим операциям локального хранилища (т. Е. Хранилище, которое появляется раньше в потоке команд, всегда появляется раньше в глобальном порядке).
- Операции локальной загрузки могут быть переупорядочены по отношению к более ранним операциям локального хранилища, так что загрузка, по-видимому, выполняется раньше, чем глобальный порядок хранения, чем локальное хранилище, но обратное (более ранняя загрузка, старое хранилище) не соответствует действительности.
Таким образом, большинство переупорядочений памяти недопустимо: загружается по отношению к каждому внешнему элементу, запоминает по отношению друг к другу и загружается по отношению к более поздним хранилищам. Тем не менее, я сказал выше, что x86 в значительной степени свободно выполняет неупорядоченные все инструкции доступа к памяти - как вы можете согласовать эти два факта?
Что ж, x86 выполняет кучу дополнительной работы, чтобы точно отследить первоначальный порядок загрузки и сохранения, и гарантирует, что никакие переупорядочения памяти, которые нарушают правила, никогда не будут видны. Например, допустим, что загрузка 2 выполняется до загрузки 1 (загрузка 1 появляется раньше в программном порядке), но обе задействованные строки кэша находились в состоянии "эксклюзивного владения" в течение периода, когда выполнялась загрузка 1 и загрузка 2: произошла переупорядочение, но локальное ядро знает, что его нельзя наблюдать, потому что никто другой не смог заглянуть в эту локальную операцию.
В сочетании с вышеупомянутыми оптимизациями, процессоры также используют умозрительное выполнение: выполняют все не по порядку, даже если возможно, что в какой-то более поздний момент какое-то ядро сможет наблюдать разницу, но на самом деле не фиксирует инструкции, пока такое наблюдение невозможно, Если такое наблюдение действительно происходит, вы откатываете процессор до более раннего состояния и попробуйте снова. Это является причиной того, что Intel упорядочивает память.
Таким образом, можно определить ISA, который вообще не допускает никакого переупорядочения, но под прикрытием переупорядочивает, но тщательно проверяет, что это не наблюдается. PA-RISC является примером такой последовательно согласованной архитектуры. Intel имеет сильную модель памяти, которая допускает один тип переупорядочения, но запрещает многие другие, но каждый чип внутри может делать больше (или меньше) переупорядочения, если они могут гарантировать игру по правилам в наблюдаемом смысле (в этом в некотором смысле, это связано с правилом "как будто", которым руководствуются компиляторы, когда речь заходит об оптимизации).
Результатом всего этого является то, что да, x86 требует барьеров памяти для предотвращения, в частности, так называемого переупорядочения StoreLoad (для алгоритмов, которые требуют этой гарантии). На практике в x86 вы не найдете много независимых барьеров памяти, потому что большинству параллельных алгоритмов также требуются атомарные операции, такие как атомарное добавление, тестирование и установка или сравнение и обмен, а в x86 все они поставляются с полными барьерами для свободно. Таким образом, использование явных инструкций барьера памяти, таких как mfence
ограничивается случаями, когда вы не выполняете атомарную операцию чтения-изменения-записи.
Переупорядочение памяти Джеффа Прешинга, зафиксированное в законе, имеет один пример, который показывает переупорядочение памяти на реальных процессорах x86, и это mfence
предотвращает это.
1 Конечно, если вы попробуете достаточно сильно, такое изменение порядка заметно! Ярким последним примером этого могут служить эксплойты Spectre и Meltdown, которые используют спекулятивное неупорядоченное выполнение и побочный канал кэша для нарушения границ безопасности защиты памяти.