Переупорядочение инструкций чтения/записи GCC

Приоритеты синхронизации Linux (spinlock, mutex, RCU) используют инструкции по защите памяти, чтобы заставить команды доступа к памяти получать повторное упорядочение. И это переупорядочение может выполняться либо самим процессором, либо компилятором.

Может ли кто-нибудь показать некоторые примеры кода GCC, где такое переупорядочение сделано? Меня интересует в основном x86. Причина, по которой я задаю это, - понять, как GCC решает, какие инструкции могут быть переупорядочены. Различные архитектуры x86 mirco (например: песчаный мост и мост с плющом) используют другую архитектуру кеша. Поэтому мне интересно, как GCC делает эффективное переупорядочение, которое помогает в производительности исполнения независимо от архитектуры кэша. Некоторый пример кода C и переупорядоченного кода, созданного GCC, был бы очень полезен. Спасибо!

Ответы

Ответ 1

Переупорядочение, которое может выполнять GCC, не связано с переупорядочением процессора (x86).

Начните с переупорядочения компилятора. Правила языка C таковы, что GCC запрещается переупорядочивать volatile нагрузки и сохранять обращения к памяти по отношению друг к другу или удалять их, когда между ними происходит точка последовательности (благодаря bobc для этого пояснения). То есть на выходе сборки появятся эти обращения к памяти и будут упорядочены точно в указанном порядке. С другой стороны, образы non- volatile могут быть переупорядочены по отношению ко всем другим доступам, volatile или нет, при условии, что (по правилу as-if) конечный результат вычисления одинаков.

Например, загрузка без volatile в C-коде может выполняться столько раз, сколько код говорит, но в другом порядке (например, если компилятор считает более удобным сделать это раньше или позже, когда больше регистры доступны). Это можно сделать меньше раз, чем говорит код (например, если копия значения по-прежнему доступна в регистре в середине большого выражения). Или его можно даже удалить (например, если компилятор может доказать бесполезность загрузки или если он полностью переместил переменную в регистр).

Чтобы предотвратить переупорядочивание компилятора в другое время, вы должны использовать специфический для компилятора барьер. Для этой цели GCC использует __asm__ __volatile__("":::"memory");.

Это отличается от переупорядочения процессора, a.k.a. модели упорядочения памяти. Древние процессоры выполняли инструкции точно в том порядке, в котором они появились в программе; Это называется программным упорядочением или сильной моделью упорядочения памяти. Однако современные процессоры иногда прибегают к "читам", чтобы работать быстрее, немного ослабив модель памяти.

То, как процессоры x86 ослабили модель памяти, задокументировано в руководствах разработчиков программного обеспечения Intel, том 3, глава 8, раздел 8.2.2 "Заказ памяти в P6 и более поздних семействах процессоров". Это, в частности, то, что он читает:

  • Чтения не переупорядочиваются с другими чтениями.
  • Писания не переупорядочиваются с помощью более старых чтений.
  • Записи в памяти не переупорядочиваются с другими записями с [некоторыми] исключениями.
  • Считывание может быть переупорядочено с помощью более старых записей в разных местах, но не с более старых записей в том же месте.
  • Считывание или запись не могут быть переупорядочены с инструкциями ввода/вывода, заблокированными инструкциями или инструкциями по сериализации.
  • Считывание не может пройти более ранние инструкции LFENCE и MFENCE.
  • Писания не могут проходить более ранние инструкции LFENCE, SFENCE и MFENCE.
  • Инструкции LFENCE не могут проходить более ранние чтения.
  • Команды SFENCE не могут передавать более ранние записи.
  • Инструкции MFENCE не могут проходить более ранние чтения или записи.

Он также дает очень хорошие примеры того, что можно и не может переупорядочить, в разделе 8.2.3 "Примеры, иллюстрирующие принципы упорядочения памяти".

Как вы можете видеть, используются инструкции FENCE для предотвращения ненадлежащего перебора процессора x86.

Наконец, вы можете быть заинтересованы в этой ссылке, которая идет более подробно и поставляется с примерами сборки, которые вы жаждете.

Ответ 2

  • Правила языка C таковы, что GCC запрещается переупорядочивать изменчивые нагрузки и хранить обращения к памяти по отношению друг к другу или удалять их.

Это неправда и довольно вводит в заблуждение. C spec не гарантирует такую ​​гарантию. См. Когда доступ к летучим объектам?

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

Традиционно программисты полагались на изменчивый метод дешевой синхронизации, но это уже не надежный метод.