Разница между 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