Какое влияние оказывают барьеры памяти на увеличение кода? - PullRequest
0 голосов
/ 04 августа 2020

Рассмотрим следующую программу: 2 потока повторяют одну и ту же функцию, которая состоит из увеличения значения переменной общего счетчика. Нет блокировки, защищающей переменную, поэтому мы говорим о программировании без блокировки. Мы также гарантируем, что потоки будут выполняться на разных ядрах / процессорах. Количество итераций достаточно велико (например, N = 100 000).

Сами операции перечислены ниже в виде псевдокода. Как и ожидалось, между инструкциями будут различные задержки, в зависимости от того, что еще делают процессоры. Ниже приведен лишь один из возможных способов их запуска.

      CPU 0           |        CPU 1
------------------------------------------
  LOAD count          |
  INC count           |     LOAD count
                      |     INC count
                      |     STORE count
  STORE count         |

Давайте не будем ориентироваться только на архитектуру x86, где модель памяти довольно сильна. Фактически, давайте рассмотрим архитектуру, «враждебную упорядочиванию памяти» (согласно C .6.1 из книги Маккенни).

Основная проблема с этим кодом заключается в том, что - конечный результат будет неправильным. Из-за состояния гонки один ЦП будет вычислять новое значение счетчика одновременно с тем, что другой делает то же самое, на основе того же значения count. В результате каждый ЦП будет записывать обратно в соответствующую строку кэша увеличенное значение count, но такое же. Это не противоречит протоколу согласованности кэша MESI, поскольку каждый ЦП получает строку кэша исключительно и записывает в нее последовательно; единственное, что прискорбно, это то, что записывается то же самое значение счетчика.

Однако меня интересует влияние установки барьеров памяти. Исключая проблему из предыдущего абзаца, принесет ли тот факт, что барьеры памяти не установлены (или они размещены неправильно) свой собственный «негативный» вклад в работу этой программы?

Если думать интуитивно о буферах хранилища и о том факте, что значения в них, вероятно, нельзя «пропустить» или «потерять», их в конечном итоге придется записать в строку кэша. Так что препятствия для записи не повлияют. Не повлияют ли также недействительные очереди и барьеры чтения? Верно ли мое предположение? Я что-то упустил?

1 Ответ

2 голосов
/ 04 августа 2020

Вы правы, барьеры памяти не могут создать атомарность. Они упорядочивают только собственные обращения этого ядра к его кеш-памяти L1d (например, слить буфер хранилища перед последующими сохранениями или загрузками = полный барьер или ожидание для более ранних загрузок для чтения кеша до выполнения любых последующих загрузок и сохранений = легкий барьер). Они не объединяют вместе несколько инструкций в транзакцию atomi c RMW.

Для создания атомарности wrt. все, что может сделать другое ядро, вам понадобится это ядро, чтобы сохранить строку кэша в состоянии MESI Exclusive или Modified от загрузки до хранилища ( Может ли num ++ быть atomi c для 'int num'? ). Барьеры этого не делают, вам нужны специальные инструкции asm, такие как x86 lock add dword [mem], 1 или на многих RIS C-подобных машинах, LL / S C retry l oop, который прерывает сохранение если строка кэша не осталась эксклюзивной для этого ядра с момента загрузки.

Барьеры памяти важны для C ++ std::atomic, потому что это также подразумевает упорядочение (получение, выпуск или seq_cst), если вы не используете memory_order_relaxed, и в этом случае компиляторы никогда не будут использовать инструкции барьера.

...