Создавайте цикл повторных попыток CAS в исходном коде только в том случае, если нет встроенного языка, реализующего требуемую атомарную операцию. Аппаратное обеспечение (особенно x86) часто работает лучше.
Java AtomicInteger
имеет getAndIncrement()
и incrementAndGet()
(по крайней мере, начиная с Java 7), который упрощает для JVM JIT-компиляцию в asm, что более эффективно, чем фактический цикл повторных попыток CAS. Это похоже на std::atomic::fetch_add()
С++ 11. См. также Практическое использование AtomicInteger.
В x86 вы хотите, чтобы ваша JVM использовала преимущества аппаратной поддержки x86 для этой операции. Это гораздо более вероятно, если вы используете функцию, которая сопоставляется непосредственно с ней, вместо цикла повторных попыток CAS. что оптимизатору придется много работать, чтобы оптимизировать реализацию без циклов.
(Существует аппаратный арбитраж шины/кэша для операций lock
ed, когда несколько ядер ЦП соревнуются за одну и ту же строку кэша; только один поток за раз может фактически владеть строкой кэша и выполнять приращение. Можно возразить, что это wait-free, даже если «шаги» — это тактовые циклы, а не инструкции процессора: вероятно, нижняя верхняя граница того, как долго операция lock
ed может ждать в любой данной системе, даже если все другие ядра работают с одной и той же строкой кэша.)
; possible x86 implementation of incrementAndGet() for a 32-bit integer
; which you'd hopefully get (after inlining and so on)
mov eax,1
lock xadd [mem], eax ; atomically do [mem]+=eax, and put the old value in eax
inc eax ; old_value += 1 to get the new value
; result in EAX
Не требуется петля.
На машинах LL/SC (большинство не-x86, таких как ARM, PowerPC, MIPS) будет цикл повторных попыток, но это не совсем CAS. И цикл повторных попыток CAS на машине LL/SC имеет дополнительные накладные расходы. Это очень незначительно, но определенно лучше позволить JVM видеть атомарную операцию, которую вы хотите напрямую. См. Атомарная очистка младшего ненулевого бита целого числа без знака для более подробного обсуждения CAS и LL/SC. Цикл CAS может оптимизироваться в чистую петлю LL/SC.
Этот вопрос также является примером случая, когда лучшим выбором (в исходном коде C++ или Java) является цикл повторных попыток CAS, потому что в языке нет атомарного примитива, который делает то, что вы хотите. (Как и любое обычное оборудование).
person
Peter Cordes
schedule
16.08.2018
[compare-and-swap]
. - person Peter Cordes   schedule 29.11.2018