Как объясняет ответ Росса, стандартный широко используемый способ - это пролить (и позже перезагрузить) что-то еще, чтобы освободить регистр tmp.
Вы стреляете себе в ногу, загружая все сначала в регистры, а не загружая по мере необходимости. Иногда вы даже можете использовать arg в качестве операнда источника памяти без отдельной загрузки mov
.
Но чтобы ответить на заглавный вопрос:
Несмотря на заголовок вопроса, мой ответ о замене 2 регистров на языке ассемблера 8086 (16 бит) точно решает проблему замены регистра с памятью эффективно, избегая xchg
из-за неявного префикса lock
, Разлейте (и позже перезагрузите) tmp reg или, в худшем случае, XOR-swap между reg и mem. Это ужасно и в основном служит иллюстрацией того, почему весь ваш подход приведет к неэффективной реализации.
(Как говорит Росс, вы, вероятно, (пока) не способны писать asm более эффективно, чем это делают компиляторы. Как только вы поймете, как создать эффективный asm (руководство по оптимизации Agner Fog и руководство по микроархам: https://agner.org/optimize/, и другие ссылки в https://stackoverflow.com/tags/x86/info) и могут обнаружить фактическую неэффективность в оптимизированном выводе компилятора, тогда вы могли бы иногда писать лучше asm вручную, если хотите. (Обычно с выводом компилятора в качестве отправной точки) Но обычно вы просто использовали бы этот опыт для настройки вашего C-кода, чтобы получить лучшую asm от вашего компилятора, если это возможно, потому что это более полезно / переносимо в долгосрочной перспективе. И это редко имеет значение, чтобы стоить писать asm от руки.
На данный момент вы, скорее всего, изучите методы для повышения эффективности asm, взглянув на вывод gcc -O3
. Но пропущенные оптимизации не редкость, и если вы заметите некоторые из них, вы можете сообщить о них в bugzilla GCC.)
Неявная семантика lock
xchg
взята из оригинального 8086. Префикс lock
существовал тогда, для использования с такими инструкциями, как add/or/and/etc [mem], reg or immediate
.
Другие упомянутые вами инструкции были добавлены позже : bts
/ btr
/ btc
в 386, xadd
в 486 и cmpxchg
не раньше Pentium. (486 имел недокументированный код операции для cmpxchg
, см. старую версию приложения NASM A для комментариев к нему).
Как вы говорите, Intel мудро решила , а не сделать lock
неявным для этих новых инструкций, хотя основной вариант использования был для атомарных операций в многопоточном коде. Машины с SMP x86 начали превращаться в штуку с 486 и Pentium, но синхронизация между потоками на машине UP не требовала lock
Это своего рода противоположный вопрос Является ли x86 CMPXCHG атомарным, если да, то зачем ему нужен LOCK?
8086 был однопроцессорным компьютером, поэтому для синхронизации между программными потоками обычный add [mem], reg
уже атомарен по отношению к прерываниям и, следовательно, к контекстным переменным . (И невозможно одновременно выполнять несколько потоков). Устаревший внешний сигнал #LOCK
, который до сих пор упоминается в документах, имеет значение только для него. Наблюдатели DMA или регистры ввода-вывода MMIO на устройствах (а не на простом DRAM).
(На современных процессорах xchg [mem], reg
на кешируемой памяти, которая не разделена по границе строки кэша, требуется только блокировка кеша, гарантируя, что строка остается в состоянии MESI Exclusive или Modified от загрузки, считывающей L1d до магазин, передающий L1d.)
Я не знаю, почему архитектор (ы) 8086 (в первую очередь Стивен Морс разработал набор инструкций) решили не делать неатомарную xchg
с доступной памятью. Может быть, на 8086 было немного медленнее, чтобы процессор утверждал #LOCK
при выполнении транзакции store + load? Но затем мы застряли с этой семантикой для остальной части x86. Дизайн x86 редко был очень дальновидным, и если основной вариант использования для xchg
был для атомарного ввода-вывода, то он сохранял размер кода, чтобы сделать lock
неявным.
Нет способа отключить неявную блокировку в xchg [mem], reg
Вам нужно использовать несколько разных инструкций.Обмен xor возможен, но очень неэффективен.Тем не менее, возможно, не так плохо, как xchg
, в зависимости от микроархитектуры и окружающего кода (насколько отстойно, чтобы все предыдущие хранилища выполнялись и фиксировали кэш L1d, прежде чем выполнять какие-либо последующие загрузки).например, некоторые из хранилищ с отсутствием кэша рейсов могут сделать его очень дорогим по сравнению с местом назначения памяти xor
, которое может оставить данные в буфере хранилища.
Компиляторы в основном никогда не используют xchg
даже между регистрами (потому что это не дешевле, чем 3 mov
инструкции для Intel , так что обычно это не полезная оптимизация глазка).Они используют его только для реализации std::atomic
хранилищ с seq_cst
порядком памяти (потому что он эффективнее, чем mov
+ mfence
на большинстве uarches: Почему хранилище std :: atomic с последовательной последовательностью использует XCHG?) и для реализации std::atomic::exchange
.
Иногда было бы полезно, если бы x86 имел микрокодированный, но не атомарный swap reg,mem
, но это не так.Нет такой инструкции.
Но особенно с x86-64, имеющим 16 регистров, эта проблема возникает только потому, что вы создали ее для себя.Оставьте себе несколько правил для вычисления.