Почему std :: atomic <T> :: is_lock_free () не является статичным, как constexpr?
9
Может кто-нибудь сказать мне, является ли std :: atomic :: is_lock_free () не статичным, а также constexpr? Не иметь смысла в том, чтобы он был нестатичным и / или неконстекстовым.
@MaxLanghof Вы имеете в виду, что не все экземпляры будут выровнены одинаково?
любопытный парень
1
Майк, нет, я не знал, но спасибо за эту подсказку; это действительно полезно для меня. Но я спрашиваю себя, почему существует решение между is_lock_free () и is_always_lock_free. Это не может быть из-за не выровненной атомики, как предлагали здесь другие, так как язык определяет невыровненный доступ, чтобы в любом случае иметь неопределенное поведение.
Все атомарные типы, за исключением std :: atomic_flag, могут быть реализованы с использованием мьютексов или других операций блокировки, а не с помощью инструкций атомарного процессора без блокировки. Атомарные типы также могут быть иногда свободными от блокировки, например, если только выровненные обращения к памяти естественным образом атомарны в данной архитектуре, неправильно выровненные объекты одного типа должны использовать блокировки.
Стандарт C ++ рекомендует (но не требует), чтобы атомарные операции без блокировки также были безадресными, то есть подходящими для связи между процессами, использующими разделяемую память.
Как уже упоминалось несколькими другими, std::is_always_lock_freeможет быть то, что вы действительно ищете.
Редактировать: Чтобы уточнить, типы объектов C ++ имеют значение выравнивания, которое ограничивает адреса их экземпляров только определенными кратными степенями two ( [basic.align]). Эти значения выравнивания определяются реализацией для основных типов и не должны равняться размеру типа. Они также могут быть более строгими, чем то, что на самом деле может поддерживать оборудование.
Например, x86 (в основном) поддерживает невыровненный доступ. Тем не менее, вы найдете большинство компиляторов alignof(double) == sizeof(double) == 8для x86, поскольку невыровненный доступ имеет множество недостатков (скорость, кэширование, атомарность ...). Но, например, #pragma pack(1) struct X { char a; double b; };или alignas(1) double x;позволяет вам иметь «не выровненные» doubleс. Поэтому, когда cppreference говорит о «выровненных обращениях к памяти», он, по-видимому, делает это с точки зрения естественного выравнивания типа для аппаратного обеспечения, не используя тип C ++ таким образом, который противоречит его требованиям к выравниванию (которое будет UB).
32-битный x86 - хороший пример того, где вы можете найти ABI alignof(double)==4. Но std::atomic<double>все равно alignof() = 8вместо проверки выравнивания во время выполнения. Использование упакованной структуры, которая выравнивает атомарные нити, нарушает ABI и не поддерживается. (GCC для 32-битных x86 предпочитает естественное выравнивание 8-байтовых объектов, но правила упаковки структуры переопределяют это и основаны только на них alignof(T), например, на i386 System V. В G ++ раньше была ошибка, когда atomic<int64_t>внутри структуры не было бы атомарной потому что это только предполагалось. GCC (для C не C ++) все еще имеет эту ошибку!)
Питер Кордес
2
Но правильная реализация C ++ 20 std::atomic_ref<double>либо полностью отклонит заниженное выравнивание double, либо проверит выравнивание во время выполнения на платформах, где это допустимо для простого doubleи int64_tбудет меньше, чем естественное выравнивание. (Потому что atomic_ref<T>действует на объект, который был объявлен как простой T, и имеет только минимальное выравнивание alignof(T)без возможности дать ему дополнительное выравнивание.)
Питер Кордес
2
См. Gcc.gnu.org/bugzilla/show_bug.cgi?id=62259 для исправления исправленной ошибки libstdc ++ и gcc.gnu.org/bugzilla/show_bug.cgi?id=65146 для исправления ошибки C, включая ошибку тестовый пример чистого ISO C11, который показывает разрыв _Atomic int64_tпри компиляции с током gcc -m32. В любом случае, я хочу сказать, что настоящие компиляторы не поддерживают недооцененные атомики и не выполняют проверки во время выполнения (пока?), Поэтому #pragma packили __attribute__((packed))просто приведут к неатомарности; объекты все равно сообщат, что они есть lock_free.
Питер Кордес
1
Но да, цель is_lock_free()состоит в том, чтобы позволить реализациям работать не так, как на самом деле; с проверками времени выполнения, основанными на фактическом выравнивании, для использования атомарных инструкций, поддерживаемых HW, или для использования блокировки.
is_lock_free зависит от конкретной системы и не может быть определена во время компиляции.
Соответствующее объяснение:
Атомарные типы также могут иногда быть свободными от блокировки, например, если только выровненные обращения к памяти естественным образом атомарны в данной архитектуре, неправильно выровненные объекты одного типа должны использовать блокировки.
std::numeric_limits<int>::maxзависит от архитектуры, но является статичным и constexpr. Я думаю, в этом нет ничего плохого, но я не покупаю первую часть рассуждений
idclev 463035818
1
Не определяет ли доступ к языку без выравнивания поведение в любом случае, так что оценка отсутствия блокировки или нет во время выполнения была бы бессмысленной?
Бонита Монтеро
1
Нет смысла выбирать между выравниваемым и не выровненным доступом, поскольку язык определяет последнее как неопределенное поведение.
Бонита Монтеро
@BonitaMontero Существует смысл «не выровненный в выравнивании объектов C ++» и «не выровненный в том, что нравится аппаратному обеспечению». Это не обязательно то же самое, но на практике они часто бывают. Пример, который вы демонстрируете, является одним из таких случаев, когда компилятор, по-видимому, имеет встроенное предположение, что оба они одинаковы, что означает только то, что на этом компиляторе нетis_lock_free смысла .
Макс
1
Вы можете быть уверены, что атом будет иметь правильное выравнивание, если есть требование выравнивания.
Бонита Монтеро
1
Я установил Visual Studio 2019 на свой Windows-ПК, и у этого устройства также есть ARMv8-компилятор. ARMv8 допускает не выровненный доступ, но сравнение и обмен, заблокированные добавления и т. Д. Должны быть выровнены. Кроме того, чистая загрузка / чистое хранение с использованием ldpили stp(пара загрузки или пара хранения 32-разрядных регистров) гарантированно будут атомарными, только если они естественным образом выровнены.
Поэтому я написал небольшую программу для проверки того, что is_lock_free () возвращает для произвольного атомного указателя. Итак, вот код:
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
Это просто returns true, ака 1.
Эта реализация выбирает для использования, alignof( atomic<int64_t> ) == 8так что каждый atomic<int64_t>правильно выровнен. Это исключает необходимость проверки выравнивания во время выполнения при каждой загрузке и хранении.
(Примечание редактора: это часто встречается; большинство реальных реализаций C ++ работают именно так. Вот почему std::is_always_lock_freeэто так полезно: потому что это обычно верно для типов, где is_lock_free()всегда верно.)
Да, большинство реализаций предпочитают давать, atomic<uint64_t>и alignof() == 8поэтому им не нужно проверять выравнивание во время выполнения. Этот старый API дает им возможность не делать этого, но на текущем HW имеет гораздо больше смысла просто требовать выравнивания (в противном случае UB, например, не атомарность). Даже в 32-битном коде, где int64_tможет быть только 4-байтовое выравнивание, atomic<int64_t>требуется 8-байтовый. Смотрите мои комментарии к другому ответу
Питер Кордес
Другими словами: если компилятор решит сделать alignofзначение для базового типа таким же, как «хорошее» выравнивание аппаратного обеспечения, тоis_lock_free всегда будет true(и так будет is_always_lock_free). Ваш компилятор здесь делает именно это. Но API существует, поэтому другие компиляторы могут делать разные вещи.
Макс
1
Вы можете быть совершенно уверены, что если язык говорит о том, что доступ без выравнивания имеет неопределенное поведение, все атомы должны быть правильно выровнены. Из-за этого ни одна реализация не выполнит никаких проверок во время выполнения.
Бонита Монтеро
@BonitaMontero Да, но в языке нет ничего, что бы запрещало alignof(std::atomic<double>) == 1(так что не было бы никакого «неприровненного доступа» в смысле C ++, следовательно, нет UB), даже если аппаратное обеспечение может гарантировать только атомарные операции без блокировки для doubles на 4 или 8-байтовые границы. Затем компилятору придется использовать блокировки в невыровненных случаях (и возвращать соответствующее логическое значение из is_lock_free, в зависимости от расположения в памяти экземпляра объекта).
is_always_lock_free
?Ответы:
Как объяснено на cppreference :
Как уже упоминалось несколькими другими,
std::is_always_lock_free
может быть то, что вы действительно ищете.Редактировать: Чтобы уточнить, типы объектов C ++ имеют значение выравнивания, которое ограничивает адреса их экземпляров только определенными кратными степенями two (
[basic.align]
). Эти значения выравнивания определяются реализацией для основных типов и не должны равняться размеру типа. Они также могут быть более строгими, чем то, что на самом деле может поддерживать оборудование.Например, x86 (в основном) поддерживает невыровненный доступ. Тем не менее, вы найдете большинство компиляторов
alignof(double) == sizeof(double) == 8
для x86, поскольку невыровненный доступ имеет множество недостатков (скорость, кэширование, атомарность ...). Но, например,#pragma pack(1) struct X { char a; double b; };
илиalignas(1) double x;
позволяет вам иметь «не выровненные»double
с. Поэтому, когда cppreference говорит о «выровненных обращениях к памяти», он, по-видимому, делает это с точки зрения естественного выравнивания типа для аппаратного обеспечения, не используя тип C ++ таким образом, который противоречит его требованиям к выравниванию (которое будет UB).Вот дополнительная информация: Каков реальный эффект от успешного доступа без выравнивания на x86?
Пожалуйста, ознакомьтесь с проницательными комментариями @Peter Cordes ниже!
источник
alignof(double)==4
. Ноstd::atomic<double>
все равноalignof() = 8
вместо проверки выравнивания во время выполнения. Использование упакованной структуры, которая выравнивает атомарные нити, нарушает ABI и не поддерживается. (GCC для 32-битных x86 предпочитает естественное выравнивание 8-байтовых объектов, но правила упаковки структуры переопределяют это и основаны только на нихalignof(T)
, например, на i386 System V. В G ++ раньше была ошибка, когдаatomic<int64_t>
внутри структуры не было бы атомарной потому что это только предполагалось. GCC (для C не C ++) все еще имеет эту ошибку!)std::atomic_ref<double>
либо полностью отклонит заниженное выравниваниеdouble
, либо проверит выравнивание во время выполнения на платформах, где это допустимо для простогоdouble
иint64_t
будет меньше, чем естественное выравнивание. (Потому чтоatomic_ref<T>
действует на объект, который был объявлен как простойT
, и имеет только минимальное выравниваниеalignof(T)
без возможности дать ему дополнительное выравнивание.)_Atomic int64_t
при компиляции с токомgcc -m32
. В любом случае, я хочу сказать, что настоящие компиляторы не поддерживают недооцененные атомики и не выполняют проверки во время выполнения (пока?), Поэтому#pragma pack
или__attribute__((packed))
просто приведут к неатомарности; объекты все равно сообщат, что они естьlock_free
.is_lock_free()
состоит в том, чтобы позволить реализациям работать не так, как на самом деле; с проверками времени выполнения, основанными на фактическом выравнивании, для использования атомарных инструкций, поддерживаемых HW, или для использования блокировки.Вы можете использовать
std::is_always_lock_free
is_lock_free
зависит от конкретной системы и не может быть определена во время компиляции.Соответствующее объяснение:
источник
std::numeric_limits<int>::max
зависит от архитектуры, но является статичным иconstexpr
. Я думаю, в этом нет ничего плохого, но я не покупаю первую часть рассужденийis_lock_free
смысла .Я установил Visual Studio 2019 на свой Windows-ПК, и у этого устройства также есть ARMv8-компилятор. ARMv8 допускает не выровненный доступ, но сравнение и обмен, заблокированные добавления и т. Д. Должны быть выровнены. Кроме того, чистая загрузка / чистое хранение с использованием
ldp
илиstp
(пара загрузки или пара хранения 32-разрядных регистров) гарантированно будут атомарными, только если они естественным образом выровнены.Поэтому я написал небольшую программу для проверки того, что is_lock_free () возвращает для произвольного атомного указателя. Итак, вот код:
И это разборка isLockFreeAtomic
Это просто
returns true
, ака1
.Эта реализация выбирает для использования,
alignof( atomic<int64_t> ) == 8
так что каждыйatomic<int64_t>
правильно выровнен. Это исключает необходимость проверки выравнивания во время выполнения при каждой загрузке и хранении.(Примечание редактора: это часто встречается; большинство реальных реализаций C ++ работают именно так. Вот почему
std::is_always_lock_free
это так полезно: потому что это обычно верно для типов, гдеis_lock_free()
всегда верно.)источник
atomic<uint64_t>
иalignof() == 8
поэтому им не нужно проверять выравнивание во время выполнения. Этот старый API дает им возможность не делать этого, но на текущем HW имеет гораздо больше смысла просто требовать выравнивания (в противном случае UB, например, не атомарность). Даже в 32-битном коде, гдеint64_t
может быть только 4-байтовое выравнивание,atomic<int64_t>
требуется 8-байтовый. Смотрите мои комментарии к другому ответуalignof
значение для базового типа таким же, как «хорошее» выравнивание аппаратного обеспечения, тоis_lock_free
всегда будетtrue
(и так будетis_always_lock_free
). Ваш компилятор здесь делает именно это. Но API существует, поэтому другие компиляторы могут делать разные вещи.alignof(std::atomic<double>) == 1
(так что не было бы никакого «неприровненного доступа» в смысле C ++, следовательно, нет UB), даже если аппаратное обеспечение может гарантировать только атомарные операции без блокировки дляdouble
s на 4 или 8-байтовые границы. Затем компилятору придется использовать блокировки в невыровненных случаях (и возвращать соответствующее логическое значение изis_lock_free
, в зависимости от расположения в памяти экземпляра объекта).