Разница между asm, asm volatile и clobbering памятью
При внедрении незакрепленных структур данных и кода синхронизации часто необходимо подавить оптимизацию компилятора. Обычно люди делают это с помощью asm volatile
с memory
в списке clobber, но иногда вы видите только asm volatile
или просто простую клавущую память asm
.
Какое влияние оказывают эти разные утверждения на генерацию кода (особенно в GCC, поскольку вряд ли он переносится)?
Для справки, это интересные варианты:
asm (""); // presumably this has no effect on code generation
asm volatile ("");
asm ("" ::: "memory");
asm volatile ("" ::: "memory");
Ответы
Ответ 1
См. Страницу "Расширенная Asm" в документации GCC.
Вы можете предотвратить удаление инструкции asm
, написав ключевое слово volatile
после asm
. [...] Ключевое слово volatile
указывает, что инструкция имеет важные побочные эффекты. GCC не удалит volatile
asm, если он достижим.
а также
Инструкция asm
без каких-либо выходных операндов будет обрабатываться идентично инструкции asm
volatile.
Ни в одном из ваших примеров не определены выходные операнды, поэтому asm volatile
формы asm
и asm volatile
ведут себя одинаково: они создают точку в коде, которую нельзя удалить (если только не доказано, что она недоступна).
Это не совсем то же самое, что ничего не делать. Посмотрите этот вопрос для примера фиктивной asm
которая изменяет генерацию кода - в этом примере код, который проходит цикл 1000 раз, векторизуется в код, который вычисляет 16 итераций цикла одновременно; но наличие asm
внутри цикла препятствует оптимизации (asm
должен быть достигнут 1000 раз).
Переполнение "memory"
заставляет GCC предполагать, что любая память может быть произвольно прочитана или записана блоком asm
, поэтому компилятор не будет переупорядочивать загрузки или сохранения через него:
Это приведет к тому, что GCC не будет хранить значения памяти, кэшированные в регистрах в инструкции ассемблера, и не будет оптимизировать хранилища или загружать в эту память.
(Это не мешает процессору переупорядочивать нагрузки и хранилища относительно другого процессора, хотя; для этого вам нужны реальные инструкции по защите памяти.)
Ответ 2
asm ("")
ничего не делает (или, по крайней мере, он не должен ничего делать.
asm volatile ("")
также ничего не делает.
asm ("" ::: "memory")
- простой забор компилятора.
asm volatile ("" ::: "memory")
AFAIK аналогичен предыдущему. Ключевое слово volatile
сообщает компилятору, что ему не разрешено перемещать этот блок сборки. Например, он может быть выровнен из цикла, если компилятор решает, что входные значения одинаковы для каждого вызова. Я не уверен, при каких условиях компилятор решит, что он достаточно разбирается в сборке, чтобы попытаться оптимизировать его размещение, но ключевое слово volatile
полностью блокирует это. Тем не менее, я был бы очень удивлен, если компилятор попытался переместить оператор asm
, который не имел заявленных входов или выходов.
Кстати, volatile
также запрещает компилятору удалять выражение, если оно решит, что выходные значения не используются. Это может произойти только при наличии выходных значений, поэтому оно не относится к asm ("" ::: "memory")
.
Ответ 3
Просто для полноты ответа Лили Баллард, Visual Studio 2010 предлагает _ReadBarrier()
, _WriteBarrier()
и _ReadWriteBarrier()
чтобы сделать то же самое (VS2010 не допускает встроенную сборку для 64-битных приложений).
Они не генерируют никаких инструкций, но влияют на поведение компилятора. Хороший пример здесь.
MemoryBarrier()
генерирует lock or DWORD PTR [rsp], 0