Требования к оформлению заказа, которые вы описываете, в точности соответствуют семантике выпуска / приобретения. (http://preshing.com/20120913/acquire-and-release-semantics/).
Проблема в том, что единица атомности для эффективных гарантированных атомарных загрузок / хранилищ составляет не более 8 байтов на всех x86 и некоторых ARM. В противном случае только 4 байта в других ARM. ( Почему целочисленное присваивание для естественно выровненной переменной атомарно в x86? ). Некоторые процессоры Intel, вероятно, на практике имеют атомарные 32 или даже 64-байтовые хранилища (AVX512), но ни Intel, ни AMD никогда не делали официальных гарантий.
Мы даже не знаем, имеют ли векторные хранилища SIMD гарантированный порядок, когда они потенциально разбивают широко выровненное хранилище на несколько выровненных по 8 байтов фрагментов. Или даже если эти куски индивидуально атомарны. атомарность каждого элемента векторной загрузки / хранения и сбора / разброса? Есть все основания полагать, что они являются атомарными для каждого элемента, даже если документация не гарантирует этого.
Если наличие больших «объектов» критично для производительности, вы можете рассмотреть возможность проверки атомарности векторной загрузки / хранения на конкретном сервере, который вам небезразличен, но вы полностью уверены в том, что можете гарантировать компилятору использовать его. , (Существуют встроенные функции.) Убедитесь, что вы тестируете между ядрами на разных сокетах, чтобы отследить случаи, подобные инструкциям SSE: какие процессоры могут выполнять атомные операции с памятью 16B? разрыв на 8-байтовых границах из-за HyperTransport между сокетами на К10 Оптерон. Это, вероятно, действительно плохая идея; Вы не можете догадаться, что, если какие-либо микроархитектурные условия могут сделать широкий вектор-хранилище неатомарным в редких случаях, даже если он обычно выглядит как атомарный.
Вы можете легко получить порядок деблокирования / получения для таких элементов массива, как
alignas(64) atomic<uint64_t> arr[1024];
. Вы просто должны спросить компилятора:
copy_to_atomic(std::atomic<uint64_t> *__restrict dst_a,
const uint64_t *__restrict src, size_t len) {
const uint64_t *endsrc = src+len;
while (src < src+len) {
dst_a->store( *src, std::memory_order_release );
dst_a++; src++;
}
}
На x86-64 он не выполняет автоматическую векторизацию или что-либо еще, потому что компиляторы не оптимизируют атомику, и потому что нет документации, что безопасно использовать векторы для хранения последовательных элементов массива атомарных элементов. :( Так что это в основном отстой. Посмотрите на это в проводнике компилятора Godbolt
Я бы посоветовал свести свои собственные с volatile __m256i*
указателями (выровненная загрузка / сохранение) и барьерами компилятора, такими как atomic_thread_fence(std::memory_order_release)
, чтобы предотвратить переупорядочение во время компиляции. Порядок элементов / атомарность должны быть в порядке (но опять же не гарантируется). И определенно не рассчитывайте, что целые 32 байта являются атомарными, просто более высокие uint64_t
элементы записываются после более низких uint64_t
элементов (и эти хранилища становятся видимыми для других ядер в этом порядке).
На ARM32 : даже атомное хранилище uint64_t
не велико. gcc использует пару ldrexd
/ strexd
(LL / SC), потому что, очевидно, нет 8-байтового хранилища атомарной чистоты. (Я скомпилировал с помощью gcc7.2 -O3 -march = armv7-a. С armv8-a в режиме AArch32, store-pair является атомарным. AArch64 также имеет атомную 8-байтовую загрузку / сохранение, конечно.)
Вы должны избегать использования обычной реализации библиотеки C memcpy
. В x86 он может использовать слабо упорядоченные хранилища для больших копий, что позволяет переупорядочивать между собственными хранилищами (но не в более поздних хранилищах, которые были не является частью memcpy
, потому что это может сломать более поздние релизы.)
movnt
Обход кэш-памяти в векторном цикле или rep movsb
в ЦП с функцией ERMSB могут создать этот эффект. Делает ли модель памяти Intel избыточность SFENCE и LFENCE? .
Или реализация memcpy
могла бы просто сделать выбор в пользу последнего (частичного) вектора перед входом в его основной цикл.
Параллельная запись + чтение или запись + запись на не atomic
типах в UB на C и C ++; вот почему memcpy
имеет такую большую свободу делать все, что захочет, в том числе использовать слабо упорядоченные хранилища, если он использует sfence
, если необходимо, чтобы убедиться, что memcpy
в целом соблюдает порядок, ожидаемый компилятором при его выдаче код для последующих mo_release
операций.
(то есть текущие реализации C ++ для x86 делают std::atomic
с допущением, что у них нет слабо упорядоченных хранилищ, о которых они могли бы беспокоиться. Любой код, который хочет, чтобы их хранилища NT уважали порядок сгенерированного компилятором кода atomic<T>
необходимо использовать _mm_sfence()
. Или, если вы пишете asm вручную, инструкцию sfence
напрямую. Или просто использовать xchg
, если вы хотите создать хранилище с последовательным выпуском и дать вашей функции asm эффект atomic_thread_fence(mo_seq_cst)
. .)