Подтвердить что ты не робот

Разница между 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");
4b9b3361

Ответ 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