Такое std::atomic::fetch_add - это операция сериализации на x86-64?

Учитывая следующий код:

std::atomic<int> counter;

/* otherStuff 1 */
counter.fetch_add(1, std::memory_order_relaxed);
/* otherStuff 2 */

Есть ли в x86-64 инструкция (скажем, менее 5-летней архитектуры), которая позволила бы переупорядочить otherStuff 1 и 2 через fetch_add или это будет всегда сериализация?

РЕДАКТИРОВАТЬ:

Похоже, это суммируется lock add барьер памяти на x86?"и, похоже, это не так, хотя я не уверен, где найти ссылку на это.

2 ответа

Решение

Сначала давайте посмотрим, что разрешено делать компилятору при использовании std::memory_order_relaxed,
Если нет зависимости между otherStuff 1/2 и атомная операция, это, конечно, может изменить порядок заявлений. Например:

g = 3;
a.fetch_add(1, memory_order_relaxed);
g += 12;

clang ++ генерирует следующую сборку:

lock   addl $0x1,0x2009f5(%rip)        # 0x601040 <a>
movl   $0xf,0x2009e7(%rip)             # 0x60103c <g>

Здесь Clang позволил себе изменить порядок g = 3 с атомным fetch_add операция, которая является законным преобразованием.

Когда используешь std::memory_order_seq_cst, вывод компилятора становится:

movl   $0x3,0x2009f2(%rip)        # 0x60103c <g>
lock   addl $0x1,0x2009eb(%rip)   # 0x601040 <a>
addl   $0xc,0x2009e0(%rip)        # 0x60103c <g>

Переупорядочение операторов не происходит, потому что компилятору не разрешено это делать. Последовательное последовательное упорядочение в операции чтения-изменения-записи (RMW) является одновременно выпуском и операцией получения, и, как таковое, не допускается (видимое) переупорядочение операторов как на уровне компилятора, так и на уровне ЦП.

Ваш вопрос: X86-64, std::atomic::fetch_addс использованием упорядоченного упорядочения является операцией сериализации.
Ответ: да, если вы не учитываете переупорядочение компилятора.

На X86 В архитектуре операция RMW всегда очищает буфер хранилища и поэтому является последовательной и последовательной последовательной операцией.

Вы можете сказать, что на X86 CPU, каждая операция RMW:

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

Целевая архитектура

В архитектуре X86 операция RMW всегда очищает буфер хранилища и, следовательно, фактически является операцией сериализации и последовательной согласованности.

Я бы хотел, чтобы люди перестали так говорить.

Это утверждение даже не имеет смысла, поскольку не существует такой вещи, как "последовательная согласованная операция", поскольку "последовательная согласованность" не является свойством какой-либо операции. Последовательно согласованное выполнение - это выполнение, при котором конечным результатом является такое чередование операций, которое дает этот результат.

Что можно сказать об этих RMW-операциях:

  • все операции перед RMW должны быть видны глобально до того, как будет видна R или W RMW
  • и никакие операции после RMW не видны до того, как будет виден RMW.

Это часть до, RMW и часть после запускаются последовательно. Другими словами, до и после RMW идет полный забор.

Приведет ли это к последовательному выполнению всей программы, зависит от характера всех глобально видимых операций программы.

Видимость и порядок выполнения

Это с точки зрения видимости. Я понятия не имею, пытаются ли эти процессоры спекулятивно выполнить код после RMW, с учетом требования корректности, что операции откатываются, если есть конфликт с побочным эффектом при параллельном выполнении (эти детали, как правило, различаются для разных поставщиков и поколения в данной семье, если это четко не указано).

Ответ на ваш вопрос может быть разным:

  • вам необходимо гарантировать правильность набора побочных эффектов (как в требовании последовательной согласованности),
  • или гарантировать надежность тестов,
  • или эта сравнительная синхронизация, независимая от версии ЦП: чтобы гарантировать что-то по результатам сравнения таймингов различных исполнений (для данного ЦП).

Языки высокого уровня и возможности процессора

Заголовок вопроса: "есть std::atomic::fetch_add операция сериализации на x86-64?"в общем виде:

"предоставляет ли OP гарантии P на ARCH"

где

  • OP - это операция высокого уровня на языке высокого уровня
  • P - желаемое свойство
  • ARCH - это конкретный целевой процессор или компилятор

Как правило, канонический ответ таков: вопрос не имеет смысла, OP - высокий уровень и не зависит от цели. Здесь есть несоответствие низкого / высокого уровня.

Компилятор связан стандартом языка (или, скорее, его наиболее разумной интерпретацией), документированным расширением, историей... не стандартом для целевой архитектуры, если эта функция не является прозрачной функцией низкого уровня языка высокого уровня..

Канонический способ получить семантику низкого уровня в C/C++ - использовать изменчивые объекты и изменчивые операции.

Здесь вы должны использовать volatile std::atomic<int> чтобы даже иметь возможность задать содержательный вопрос об архитектурных гарантиях.

Текущая генерация кода

Значимый вариант вашего вопроса будет использовать этот код:

volatile std::atomic<int> counter;

/* otherStuff 1 */
counter.fetch_add(1, std::memory_order_relaxed);

Этот оператор сгенерирует атомарную операцию RMW, которая в этом случае "является операцией сериализации" на ЦП: все операции, выполненные ранее в коде сборки, завершаются до запуска RMW; все операции, следующие за RMW, ждут, пока RMW не завершится для запуска (с точки зрения видимости).

Затем вам нужно будет узнать о неприятностях семантики volatile: volatile применяется только к этим изменчивым операциям, поэтому вы все равно не получите общих гарантий относительно других операций.

Нет гарантии, что высокоуровневые операции C++ перед изменчивыми операциями RMW будут упорядочены раньше в коде сборки. Для этого вам понадобится "барьер компилятора". Эти барьеры не переносимые. (И не нужно здесь, так как это в любом случае глупый подход.)

Но если вам нужна эта гарантия, вы можете просто использовать:

  • операция выпуска: чтобы убедиться, что предыдущие глобально видимые операции завершены
  • операция получения: чтобы следующие глобально видимые операции не запускались раньше
  • Операция RMW над объектом, видимым для нескольких потоков.

Так почему бы не сделать вашу операцию RMW ack_rel? Тогда он даже не должен был быть изменчивым.

Возможные варианты RMW в семействе процессоров

Есть ли инструкция в x86-64 (скажем, для архитектуры менее 5 лет), которая бы

Другой подвопрос - возможные варианты набора команд. Поставщики могут вводить новые инструкции и способы проверки их доступности во время выполнения; и компиляторы могут даже генерировать код для определения их доступности.

Любая функция RMW, которая следовала бы существующей традиции (1) строгого упорядочивания обычных операций с памятью в этом семействе, должна была бы уважать традиции:

  • Общий порядок магазина: все операции магазина упорядочены, неявно ограждены; другими словами, в каждом ядре есть буфер хранения строго для неспекулятивных операций хранения, который не переупорядочивается и не разделяется между ядрами;
  • каждое сохранение - это операция освобождения (для предыдущих обычных операций с памятью);
  • предположительно запущенные загрузки завершаются по порядку и по завершении проверяются: любая ранняя загрузка для местоположения, которое затем было заблокировано в кэше, отменяется, и вычисление перезапускается с последним значением;
  • грузы - это приобретаемые операции.

Тогда любая новая (но традиционная) операция RMW должна быть как операцией получения, так и операцией выпуска.

(Примеры возможной воображаемой операции RMW, которые будут добавлены в будущем: xmult а также xdiv.)

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

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

(1) Традиции для гарантий операций с памятью являются руководящими принципами при проектировании ЦП, а не гарантиями каких-либо операций функций: по определению операции, которые еще не существуют, не имеют никаких гарантий в отношении своей семантики, кроме гарантий целостности памяти, то есть гарантировать, что никакая будущая операция не нарушит ранее установленные привилегии и гарантии безопасности (ни одна непривилегированная инструкция, созданная в будущем, не может получить доступ к не отображенному адресу памяти...).

Когда используешь std::memory_order_relaxed единственная гарантия состоит в том, что операция является атомной. Все что угодно вокруг операции может быть переупорядочено по желанию либо компилятором, либо процессором.

С https://en.cppreference.com/w/cpp/atomic/memory_order:

Расслабленная операция: нет ограничений синхронизации или упорядочения, налагаемых на другие операции чтения или записи, гарантируется только атомарность этой операции (см. Упорядоченное упорядочение ниже)

Другие вопросы по тегам