Разница между asm, asm volatile и сгущающейся памятью

При реализации структур данных без блокировки и временного кода часто необходимо подавлять оптимизацию компилятора. Обычно люди делают это, используя asm volatile с memory в списке тупиков, но иногда вы видите только asm volatile или просто равнина asm забивающая память.

Какое влияние эти различные операторы оказывают на генерацию кода (особенно в GCC, поскольку он вряд ли будет переносимым)?

Просто для справки, это интересные варианты:

asm ("");   // presumably this has no effect on code generation
asm volatile ("");
asm ("" ::: "memory");
asm volatile ("" ::: "memory");

3 ответа

Решение

См. Страницу "Расширенная Asm" в документации GCC.

Вы можете предотвратить asm Инструкция от удаления, написав ключевое слово volatile после asm, [...] volatile Ключевое слово указывает, что инструкция имеет важные побочные эффекты. GCC не удалит volatile ASM, если это достижимо.

а также

asm Инструкция без каких-либо выходных операндов будет обрабатываться идентично asm инструкция.

Ни в одном из ваших примеров не указаны выходные операнды, поэтому asm а также asm volatile формы ведут себя одинаково: они создают в коде точку, которую нельзя удалить (если только она не окажется недоступной).

Это не совсем то же самое, что ничего не делать. Смотрите этот вопрос на примере манекена asm который изменяет генерацию кода - в этом примере код, который проходит цикл 1000 раз, векторизуется в код, который рассчитывает 16 итераций цикла одновременно; но наличие asm внутри цикла препятствует оптимизации (asm должно быть достигнуто 1000 раз).

"memory" Clobber заставляет GCC предполагать, что любая память может быть произвольно прочитана или записана asm block, поэтому компилятор не будет переупорядочивать нагрузки или сохранять их:

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

(Это не мешает процессору переупорядочивать загрузки и сохранения относительно другого процессора; для этого вам нужны реальные инструкции по защите памяти.)

asm ("") ничего не делает (или, по крайней мере, он не должен ничего делать.

asm volatile ("") тоже ничего не делает.

asm ("" ::: "memory") это простой забор компилятора.

asm volatile ("" ::: "memory") AFAIK такой же, как предыдущий. volatile Ключевое слово сообщает компилятору, что нельзя перемещать этот блок сборки. Например, он может быть выведен из цикла, если компилятор решит, что входные значения одинаковы при каждом вызове. Я не совсем уверен, при каких условиях компилятор решит, что он достаточно разбирается в сборке, чтобы попытаться оптимизировать ее размещение, но volatile Ключевое слово подавляет это полностью. Тем не менее, я был бы очень удивлен, если бы компилятор попытался переместить asm заявление, которое не имело заявленных входов или выходов.

Между прочим, volatile также предотвращает удаление выражения компилятором, если он решает, что выходные значения не используются. Это может произойти только при наличии выходных значений, поэтому это не относится к asm ("" ::: "memory"),

Для полноты ответа Кевина Балларда Visual Studio 2010 предлагает _ReadBarrier(), _WriteBarrier() и _ReadWriteBarrier(), чтобы сделать то же самое (VS2010 не допускает встроенную сборку для 64-битных приложений).

Они не генерируют никаких инструкций, но влияют на поведение компилятора. Хороший пример здесь

MemoryBarrier() генерирует lock or DWORD PTR [rsp], 0

Другие вопросы по тегам