Атомность каждого элемента векторной загрузки / хранения и сбора / разброса?

Рассмотрим массив как atomic<int32_t> shared_array[], Что делать, если вы хотите SIMD векторизации for(...) sum += shared_array[i].load(memory_order_relaxed)?. Или искать в массиве первый ненулевой элемент или обнулять его диапазон? Это, вероятно, редко, но рассмотрим любой вариант использования, где разрыв внутри элемента не разрешен, но переупорядочение между элементами хорошо. (Возможно, поиск, чтобы найти кандидата на CAS).

Я думаю, что выровненные по x86 векторные загрузки / хранилища будут безопасны на практике для SIMD с mo_relaxed операций, потому что любой разрыв будет происходить только на границах 8B в худшем случае на текущем оборудовании (потому что это то, что делает естественно выровненные 8B доступными к атомарному1). К сожалению, в руководствах Intel сказано только:

"Инструкция x87 или инструкция SSE, которая осуществляет доступ к данным, большим, чем четырехугольное слово, может быть реализована с использованием нескольких обращений к памяти"

Нет гарантии, что доступ к этим компонентам будет естественным, неперекрывающимся или чем-то еще. (Забавный факт: x87 10-байтовый fld m80 По словам Агнера Фога, загрузка, выполненная двумя загрузочными мопами и двумя мерами ALU на Haswell, предположительно qword + word.)

Если вы хотите векторизовать таким образом, чтобы в будущем, как говорят текущие руководства по x86, работали на всех будущих процессорах x86, вы можете загрузить / сохранить в виде фрагментов 8B с movq / movhps,

Или, может быть, вы могли бы использовать 256bvpmaskmovdс полностью истинной маской, потому что в разделе "Операция" руководства она определяется в терминах нескольких отдельных 32-разрядных нагрузок, напримерLoad_32(mem + 4), Означает ли это, что каждый элемент действует как отдельный 32-битный доступ, гарантируя атомарность внутри этого элемента?

(На реальном оборудовании это 1 загрузка и 2 порта5 uops на Haswell или на Ryzen только 1 или 2 загрузки +ALU uops (128 / 256). Я предполагаю, что это для случая, когда не нужно исключать исключения из элементов, которые идут в неотображенную страницу, так как это может быть медленнее (но IDK, если ему нужна помощь с микрокодом). В любом случае, это говорит нам, что это по крайней мере так же атомарно, как обычноvmovdqaзагрузить на Haswell, но это ничего не говорит нам о x86 Deathstation 9000, где векторные обращения 16B / 32B разбиты на однобайтовые, так что внутри каждого элемента может быть разрыв.

Я думаю, что в действительности можно с уверенностью предположить, что вы не увидите разрыв в 16, 32 или 64-битном элементе длявыровненных векторных загрузок / хранилищ на любом реальном процессоре x86, потому что это не имеет смысла для эффективной реализации, которая уже 64-разрядные скалярные скалярные хранилища должны быть естественными, но интересно знать, насколько далеко на самом деле идут гарантии в руководствах.)


Сбор (AVX2,AVX512) / Scatter (AVX512)

Инструкции какvpgatherddболее очевидно состоят из нескольких отдельных 32b или 64b доступов. Форма AVX2 задокументирована как выполняющая несколько FETCH_32BITS(DATA_ADDR);по-видимому, на это распространяются обычные гарантии атомарности, и каждый элемент будет собираться атомарно, если он не пересекает границу.

Сборки AVX512 документированы в PDF-руководстве Intel по insn как
DEST[i+31:i] <- MEM[BASE_ADDR + SignExtend(VINDEX[i+31:i]) * SCALE + DISP]), 1) для каждого элемента в отдельности. (Порядок: элементы могут быть собраны в любом порядке, но ошибки должны доставляться в порядке справа налево. Упорядочение памяти с другими инструкциями следует модели упорядочения памяти Intel- 64.)

Разброс AVX512 документируется (страница 1802 предыдущей ссылки) таким же образом. Атомность не упоминается, но они охватывают некоторые интересные угловые случаи:

  • Если два или более целевых индекса полностью перекрываются, "более ранние" записи могут быть пропущены.

  • Элементы могут быть разбросаны в любом порядке, но ошибки должны доставляться в порядке справа налево

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

  • Только записи в перекрывающиеся индексы векторов гарантированно упорядочены относительно друг друга (от LSB до MSB исходных регистров). Обратите внимание, что это также включает частично перекрывающиеся векторные индексы. Записи, которые не перекрываются, могут происходить в любом порядке. Упорядочение памяти с другими инструкциями следует модели упорядочения памяти Intel- 64. Обратите внимание, что это не учитывает неперекрывающиеся индексы, которые отображаются на те же адреса физических адресов.

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

Я включил последние два, потому что это интересные угловые случаи, о которых я даже не думал удивляться. Самоизменяющийся случай забавен, хотя я думаюrep stosdбудет иметь ту же проблему (это также прерываемый, используяrcxотслеживать прогресс).

Я думаю, что атомарность является частью модели упорядочения памяти Intel- 64, поэтому тот факт, что они упоминают об этом и не говорят ничего другого, по-видимому, подразумевает, что доступ к элементу является атомарным. (Сбор двух соседних элементов 4B почти наверняка не считается одним доступом 8B.)


Какие инструкции по загрузке / сохранению векторов гарантируются в руководствах x86 как атомарные для каждого элемента?

Экспериментальное тестирование на реальном оборудовании почти наверняка скажет мне, что на моем процессоре Skylake все атомарно, и вопрос не в этом.Я спрашиваю, правильна ли моя интерпретация руководств дляvmaskmov/vpmaskmov нагрузки, а для сбора / разброса.

(Если есть какая-либо причина сомневаться в том, что реальное оборудование будет по-прежнему элементарным для простого movdqa загружает, это тоже был бы полезный ответ.)


  1. Сноска: основы атомности x86:

В x86, согласно руководствам Intel и AMD, естественные нагрузки и хранилища 8B или уже гарантированно будут атомарными. Фактически, для кэшированных обращений любой доступ, который не пересекает границу 8B, также является атомарным. (На Intel P6 и более поздних версиях дайте более сильную гарантию, чем AMD: невыровненный в строке кэша (например, 64B) является атомарным для кэшированного доступа).

Векторные нагрузки / хранилища 16B или шире не гарантируются как атомарные. Они находятся на некоторых процессорах (по крайней мере, для кэшированного доступа, когда наблюдателями являются другие процессоры), но даже атомарный доступ шириной 16B к кэш-памяти L1D не делает его атомарным. Например, протокол когерентности HyperTransport между сокетами для AMD K10 Opteron вводит разрыв между половинами выровненного вектора 16B, даже если тестирование потоков в одном и том же сокете (физическом ЦП) не показывает разрывов.

(Если вам нужна полная атомная нагрузка 16B или хранилище, вы можете взломать ее с помощью lock cmpxchg16b как GCC делает для std::atomic<T>, но это ужасно для производительности. См. Также Атомная двойная с плавающей точкой или SSE/AVX векторная загрузка / сохранение на x86_64.)

0 ответов

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