Почему стандартные библиотеки не реализуют std::atomic для структур менее 8 байт без блокировки?

Предполагая, что архитектура может поддерживать 8-байтовые скаляры без блокировки для std::atomic. Почему стандартные библиотеки не предоставляют аналогичные специализации для структур размером менее 8 байт?

Простая реализация такой специализации std::atomic может просто сериализовать/десериализовать (с std::memcpy) структуру в эквивалентную std::uintx_t, где x — это ширина структуры в битах (округленная до ближайшей степени 2, которая больше или равно ширине структуры). Это было бы хорошо определено, потому что std::atomic требует, чтобы эти структуры были тривиально копируемыми.

Например. https://godbolt.org/z/sxSeId, здесь Something всего 3 байта, но реализация вызывает __atomic_load и __atomic_exchange, оба из которых используют таблицу блокировки.


person Curious    schedule 28.04.2019    source источник
comment
gcc делает это правильно, если вы делаете структуру 4 байта (но не 3), см. godbolt.org/z/d1OCmG< /а>. лязг нет.   -  person Paul Sanders    schedule 29.04.2019
comment
@PaulSanders Интересно, интересно, почему 3 байта не работают ..   -  person Curious    schedule 29.04.2019
comment
Не существует инструкции x86, которая загружает/сохраняет 3 байта, не говоря уже об атомарности.   -  person rustyx    schedule 29.04.2019
comment
@rustyx Ах, извините, но вы всегда можете взять больше, чем размер до следующей степени 2, верно? Раздел §[atomics.types.generic]p3 разрешает это - Представление атомарной специализации не обязательно должно иметь тот же размер, что и соответствующий тип аргумента. Я полагаю, что с этим есть проблемы с переносимостью?   -  person Curious    schedule 29.04.2019
comment
@rustyx В большинстве компиляторов структура дополняется до 4 байтов. Так как в sizeof(S) никогда не бывает 3.   -  person Passer By    schedule 29.04.2019
comment
Но это может быть 3 байта, и тогда вам вдруг потребуется атомарный доступ к 3 байтам, и у вас проблемы.   -  person gnasher729    schedule 29.04.2019
comment
@Curious: Если вы установите выравнивание структуры равным 4, то она отлично работает даже в GCC.   -  person Nicol Bolas    schedule 29.04.2019
comment
@NicolBolas, кажется, что clang не godbolt.org/z/N6P6Hs. Есть ли причина для этого или это просто то, что нужно исправить?   -  person Curious    schedule 29.04.2019
comment
@Curious: Когда я сказал принудительное выравнивание, я имел в виду alignas(4).   -  person Nicol Bolas    schedule 29.04.2019
comment
@NicolBolas Ах, интересно, есть идеи, почему однобайтовое выравнивание не работает? Я не понимал, что установка более высокого выравнивания заставит его внезапно использовать атомарные инструкции.   -  person Curious    schedule 29.04.2019


Ответы (1)


К сожалению, atomic<T> в Linux (?) не выравнивает / не дополняет размер до степени двойки. std::atomic<Something> arr[10] имеет sizeof(arr) = 30. (https://godbolt.org/z/WzK66xebr)


Используйте struct Something { alignas(4) char a; char b,c; };
(не alignas(4) char a,b,c;, поскольку в этом случае каждый символ будет дополнен до 4 байтов, чтобы их можно было выровнять.)

Объекты с размером, отличным от степени двойки, могут пересекать границу строки кэша, поэтому использование более широкой 4-байтовой загрузки не всегда возможно.

Кроме того, чистые хранилища всегда должны использовать CAS (например, lock cmpxchg), чтобы не изобретать записи в байт вне объекта: очевидно, вы не можете использовать два отдельных хранилища mov (2 байта + 1 байт), потому что это не будет атомарный, если только вы не сделаете это внутри транзакции TSX с циклом повтора.


Загрузка/сохранение x86 гарантированно атомарны только для доступа к памяти, которые не пересекают 8-байтовую границу. (На некоторых поставщиках/архивах граница строки кэша. Или для возможно некэшируемых загрузок/хранилищ, в основном, вам нужно естественное выравнивание). Почему целочисленное назначение на естественно выровненном атомарная переменная на x86?

Ваш struct Something { char a, b, c; }; не требует выравнивания, поэтому нет правила C++, которое не позволяет объекту Something занимать 2 строки кэша. Это сделало бы его простую загрузку/сохранение определенно неатомарной.

gcc и clang решили реализовать atomic<T> с тем же макетом/представлением объектов, что и T (независимо от того, не блокируется он или нет). Следовательно, atomic<Something> — это 3-байтовый объект. Таким образом, массив atomic<Something> обязательно имеет некоторые из этих объектов, перекрывающих границы строки кэша, и не может иметь отступы за пределами объекта, потому что массивы работают иначе в C. sizeof() = 3 говорит вам о структуре массива. Это делает atomic<Something> без блокировок невозможным. (Если только вы не загрузите/сохраните lock cmpxchg, чтобы быть атомарным даже при разбиении строк кэша, что приведет к значительному снижению производительности в тех случаях, когда это произошло. Лучше заставить разработчиков исправить свою структуру.)

Класс atomic<T> может иметь более высокие требования к выравниванию, чем T, например, atomic<int64_t> имеет alignof(atomic_int64_t) == 8, в отличие от alignof(int64_t) == 4 на многих 32-битных платформах (включая i386 System V ABI).

Если бы gcc/clang не принял решение сохранить макет прежним, они могли бы иметь atomic<T> дополняющие маленькие объекты до следующей степени 2 и добавлять выравнивание, чтобы они могли быть без блокировок. Это был бы правильный выбор реализации. Я не могу думать ни о каких минусах.


Забавный факт, поддержка gcc C11 _Atomic немного нарушена на 32-битных платформах с 64-битные безблокировочные атомарные объекты: _Atomic int64_t могут быть смещены внутри структур, что приведет к разрыву. Они до сих пор не обновили ABI для типов _Atomic, чтобы иметь естественное выравнивание.

Но в g++ C++11 std::atomic используется класс-шаблон в заголовке, который недавно исправил эту ошибку (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65147); гарантируя, что atomic<T> имеет естественное выравнивание (до некоторой степени размера 2), даже если T имеет выравнивание ‹ размера. Таким образом, они никак не могут охватить любую границу шире, чем они есть.

person Peter Cordes    schedule 03.08.2019
comment
О, разделение кеша на самом деле имеет смысл, спасибо! - person Curious; 03.08.2019