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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Это неправда и довольно вводит в заблуждение. C spec не дает такой гарантии. См., Когда Доступен Неустойчивый Объект?

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

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

    Давайте будем гением компьютера.