Включает ли забор памяти ядро - PullRequest
4 голосов
/ 12 февраля 2020

Задав этот вопрос , я понял, что инструкция atomi c, такая как test-and-set, не будет включать ядро. Только если процесс нужно перевести в режим ожидания (чтобы дождаться получения блокировки) или разбудить (потому что он не смог получить блокировку, но теперь может), тогда ядро ​​должно быть задействовано для выполнения операций планирования.

Если так, значит ли это, что забор памяти, такой как std::atomic_thread_fence в c ++ 11, также не будет включать ядро?

Ответы [ 2 ]

6 голосов
/ 12 февраля 2020

std :: atomi c не включает ядро ​​ 1

Почти на всех обычных процессорах (типа, которые мы программируем в реальной жизни), барьер памяти инструкции являются непривилегированными и используются компилятором напрямую. Точно так же компиляторы знают, как генерировать такие команды, как x86 lock add [rdi], eax для fetch_add (или lock xadd, если вы используете возвращаемое значение). Или на других ISA, буквально те же инструкции по барьеру, которые они используют до / после загрузки, хранения и RMW, чтобы дать требуемый порядок. https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/

На некоторых произвольное гипотетическое аппаратное обеспечение и / или компилятор, все, конечно, возможно, даже если это будет катастрофически плохо для производительности.

В asm, барьер просто заставляет это ядро ​​ждать, пока некоторые предыдущие (программные) операции видны другим ядрам. Это чисто локальная операция. (По крайней мере, так устроены ЦП реального слова, так что последовательная согласованность может быть восстановлена ​​только с локальными барьерами для управления локальным упорядочением операций загрузки и / или хранения. Все ядра имеют согласованное представление о кеше, поддерживаемое с помощью протокола, подобного MESI. Существуют несогласованные системы с разделяемой памятью, но реализации не запускают через них C ++ std :: thread и обычно не запускают ядро ​​с одним образом системы.)

Сноска 1: (Даже атомы без блокировки обычно используют легкие блокировки).

Кроме того, ARM до ARMv7, по-видимому, не имел надлежащих инструкций барьера памяти . На ARMv6 G CC использует mcr p15, 0, r0, c7, c10, 5 в качестве барьера.
До этого (g++ -march=armv5 и ранее) G CC не знает, что делать, и вызывает __sync_synchronize (вспомогательная функция libatomi c G CC), которая, мы надеемся, каким-то образом реализована для любой машины, на которой фактически выполняется код. Это может включать системный вызов в гипотетической многоядерной системе ARMv5, но более вероятно, что двоичный файл будет работать в системе ARMv7 или v8, где функция библиотеки может запускать dmb ish. Или, если это одноядерная система, то, я думаю, это может быть неработоспособность. (Упорядочение памяти C ++ относится к другим потокам C ++, а не к порядку памяти, который видят возможные аппаратные устройства / DMA. Обычно реализации предполагают многоядерную систему, но эта библиотечная функция может быть в случае, когда может использоваться только одноядерная реализация. .)


На x86, например, std::atomic_thread_fence(std::memory_order_seq_cst) компилируется в mfence. Слабые барьеры, такие как std::atomic_thread_fence(std::memory_order_release), должны блокировать только переупорядочение во время компиляции; Аппаратная модель памяти времени выполнения x86 уже acq / rel (seq-cst + буфер хранения). Так что нет никаких asm-инструкций, соответствующих барьеру. (Одной из возможных реализаций библиотеки C ++ может быть GNU C asm("" ::: "memory");, но в GCC / clang есть встроенные барьеры.)

std::atomic_signal_fence только когда-либо блокирует переупорядочение во время компиляции , даже для слабо упорядоченных ISA, поскольку все реальные ISA гарантируют, что при выполнении в одном потоке видит свои собственные операции как происходящие в программном порядке. (Аппаратное обеспечение реализует это, загружая sn oop буфер хранилища текущего ядра). VLIW и IA-64 EPI C, или другие механизмы ISA явного параллелизма (например, Mill с его нагрузками с задержкой видимости), все еще позволяют компилятору генерировать код, который соблюдает любые гарантии упорядочения C ++, включая барьер, если асинхронный c Сигнал (или прерывание для кода ядра) поступает после любой инструкции.


Вы можете посмотреть на code-gen самостоятельно в проводнике компилятора Godbolt :

#include <atomic>
void barrier_sc(void) {
    std::atomic_thread_fence(std::memory_order_seq_cst);
}

x86: mfence.
МОЩНОСТЬ: sync.
AArch64: dmb ish (полный барьер на «внутреннем разделяемом» домене когерентности).
ARM с gcc -mcpu=cortex-a15 (или -march=armv7): dmb ish
RIS C -V: fence iorw,iorw

void barrier_acq_rel(void) {
    std::atomic_thread_fence(std::memory_order_acq_rel);
}

x86: ничего
POWER: lwsync (облегченный син c).
AArch64: все еще dmb ish
ARM: все еще dmb ish
RIS C -V: все еще fence iorw,iorw

void barrier_acq(void) {
    std::atomic_thread_fence(std::memory_order_acquire);
}

x86: ничего
МОЩНОСТЬ: lwsync (легкий вес c).
AArch64: dmb ishld (барьер загрузки, не нужно истощать буфер хранилища)
ARM: все еще dmb ish, даже с -mcpu=cortex-a53 (ARMv8): /
RIS C -V: все еще fence iorw,iorw

1 голос
/ 12 февраля 2020

Как в этом, так и в ссылочном вопросе вы смешиваете:

  • примитивы синхронизации в области ассемблера, например cmpxchg и заборы
  • process / Синхронизация потоков, например futexes

Что означает «включает ядро»? Я предполагаю, что вы имеете в виду «(p) синхронизация потоков»: поток переводится в спящий режим и будет активирован, как только другой процесс / поток выполнит заданное условие.

Однако примитивы test-and-set типа cmpxchg и ограждения памяти - это функции, предоставляемые ассемблером микропроцессора. Примитивы синхронизации ядра в конечном итоге основаны на них для обеспечения синхронизации системы и процессов с использованием общего состояния в пространстве ядра, скрытом за вызовами ядра.

Вы можете посмотреть на источник futex , чтобы получить свидетельство it.

Но нет, ограничения памяти не затрагивают ядро: они переводятся в простых операций ассемблера . Так же, как cmpxchg.

...