`atomic_compare_exchange_strong_explicit ()` - что делают различные комбинации параметров «success» и «fail», если они не равны? - PullRequest
1 голос
/ 10 марта 2020

Функция atomic_compare_exchange_strong_explicit() принимает два параметра memory_order, success и failure (как и atomic_compare_exchange_weak_explicit()). Выбрав Стандарты C11 / C18, я обнаружил, что допустимые значения для success и failure:

    success = memory_order_relaxed     failure = memory_order_relaxed

    success =             _release     failure =             _relaxed

    success =             _consume     failure =             _relaxed
                                             or              _consume

    success =             _acquire     failure =             _relaxed
          or              _acq_rel           or              _consume (?)
                                             or              _acquire

    success =             _seq_cst     failure =             _relaxed
                                             or              _consume (?)
                                             or              _acquire
                                             or              _seq_cst

Стандарт также гласит:

Далее, если Сравнение истинно, память зависит от значения успеха, а если сравнение ложно, память зависит от значения ошибки. Эти операции являются атомами c операции чтения-изменения-записи (5.1.2.4).

Ваши устройства ARM, POWER-P C и другие устройства "LL / S C" выполняют Load-Link / Cmp / Store-Conditional последовательность для реализации atomi c -cmp-exchange, где Load-Link может или не может быть _acquire , а Store-Conditional может или не может быть _Release .

Итак, я могу понять: успех = _acq_rel и провал = _acquire .

То, что я не могу понять - (среди прочих): успех = _acq_rel и сбой = _relaxed . Конечно, чтобы достичь _acq_rel , Load-Link должен быть _acquire ? Если cmp дает сбой, тогда, конечно, уже слишком поздно понижать его до _relaxed ?

Каково значение комбинаций параметров success и failure (если они не равно)?

[Всегда возможно, что я был сбит с толку Стандартом, и что порядок памяти failure может фактически быть только половиной чтения любого порядка памяти success есть.]

1 Ответ

4 голосов
/ 10 марта 2020

Одним из способов реализации нагрузки загрузки в asm на некоторых ISA является обычная нагрузка , за которой следует забор, например, на ISA, таких как PowerP C или ARM, до того как ARMv8 представил ldar / ldaxr , Этот более поздний забор может быть пропущен, если порядок сбоя не включает в себя приобретение.

LL / S C CAS_weak может выглядеть примерно так в псевдоасмере для реального ISA:

   ll    r0, mem
   cmp   r0, r1
   jne  .fail        # early-out compare fail
   sc    r2, mem     # let's pretend this sets CF condition code on SC failure
   jc   .fail        # jump if SC failed

   lwsync              # LoadLoad, StoreStore, and LoadStore but not StoreLoad
   ... CAS success path

.fail:   # we jump here without having executed any barriers
   ... CAS failure path

Это (я думаю) может быть допустимой реализацией mem.compare_exchange_weak(r1, r2, mo_acquire, mo_relaxed); на некоторых типах машин.

Это только операция получения, поэтому весь RMW может переупорядочиваться с более ранними операциями ( природа LL / S C удерживает их в глобальном порядке). Перед операцией нет барьера, только после.

lwsync - это инструкция барьера PowerP C, которая блокирует все переупорядочения, кроме StoreLoad (т. Е. Не хранит буфер * flu sh). https://preshing.com/20120913/acquire-and-release-semantics/ и https://preshing.com/20120930/weak-vs-strong-memory-models/


Чтобы реализовать CAS(..., acq_rel, relaxed) на большинстве ISA, мы также установили бы барьер до LL / S C (чтобы отделить его от более ранних операций, создав выпускную часть). Это будет выполняться даже на пути сбоя, но не создаст семантику получения. Он не будет отделять нагрузку от более поздних операций.

AFAIK, вы не захотите ставить забор между LL и S C, чтобы, возможно, пропустить, если сравнение не удалось. Это бы удлинило транзакцию и дало бы ей больше шансов потерпеть неудачу из-за активности других потоков.

Как всегда при реализации модели памяти C ++ поверх реального asm, вы делаете что-то настолько сильное, насколько это необходимо, но не более сильное , учитывая ограничения того, что обеспечивает базовый ISA. Большинство ISA не позволяют сделать так, чтобы путь сбоя CAS (acq_rel, relaxed) был фактически таким же дешевым, как простая расслабленная нагрузка , либо потому, что это невозможно, либо потому, что это повредило бы производительности обычного случая. Но в некоторых случаях он все еще может быть на меньше дороже, чем если бы сторона отказа должна была получить семантику.

Некоторые ISA (например, ARM), очевидно, имеют только полные барьеры (dsb ish), поэтому даже acq_rel заканчивает тем, что истощал буфер хранилища. (Таким образом, ARMv8, представляющий хранилища с загрузкой и последовательным выпуском, был очень приятным, поскольку он идеально соответствует семантике C ++ seq-cst и потенциально может быть намного дешевле барьера.)

...