Здравствуйте, B0FEE664, Вы писали:
BFE>и получилось, что volatile bool обгоняет std::atomic_flag на порядок, BFE>Так и должно быть или я чего-то напортачил ?
У методов store/load/test/clear ты не указываешь упорядочивание, а по умолчанию там выбирается максимально надёжный и максимально медленный std::memory_order_seq_cst.
Укажи там, например, std::memory_order_relaxed — и всё заработает куда как бодрее.
Так что если в приоритете скорость кода, то указывай явно достаточное упорядочивание. Если же в приоритете простота и скорость написания кода — то оставляй как есть.
BFE>а std::atomic<bool> обгоняет std::atomic_flag.
В общем случае атомарная запись в память быстрее чем запись и чтение (.test_and_set), так что так может быть.
Re: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, Abyx, Вы писали:
A>во-первых, volatile+многопоточность=гонки=UB
Тем не мене получается, что использование volatile bool может быть оптимизацией для некоторых процессоров. А именно, когда операция атомарна, процессор имеет одно ядро и гонки устраняются другими способами (или не важны).
И каждый день — без права на ошибку...
Re[3]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, Abyx, Вы писали:
A>>во-первых, volatile+многопоточность=гонки=UB BFE>Тем не мене получается, что использование volatile bool может быть оптимизацией для некоторых процессоров. А именно, когда операция атомарна, процессор имеет одно ядро и гонки устраняются другими способами (или не важны).
всё так, но если у тебя процессор с 1 ядром, ты хочешь большего быстродействия (оптимизации программы) — то это повод использовать процессор хотя бы с 8 ядрами.
In Zen We Trust
Re[3]: volatile bool vs std::atomic_flag vs std::atomic<bool>
A>>во-первых, volatile+многопоточность=гонки=UB BFE>Тем не мене получается, что использование volatile bool может быть оптимизацией для некоторых процессоров. А именно, когда операция атомарна, процессор имеет одно ядро и гонки устраняются другими способами (или не важны).
Твой atomic_flag скорее всего генерирует барьер, чтобы разницы между volatile bool и atomic_flag-ом не было, нужно использовать memory_order_relaxed. Какой смысл использовать все это в одном потоке — загадка.
Re[4]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, ELazin, Вы писали:
A>>>во-первых, volatile+многопоточность=гонки=UB BFE>>Тем не мене получается, что использование volatile bool может быть оптимизацией для некоторых процессоров. А именно, когда операция атомарна, процессор имеет одно ядро и гонки устраняются другими способами (или не важны).
EL>Какой смысл использовать все это в одном потоке — загадка.
Так проще измерить время выполнения операции.
EL>Твой atomic_flag скорее всего генерирует барьер, чтобы разницы между volatile bool и atomic_flag-ом не было, нужно использовать memory_order_relaxed.
А какой смысл в memory_order_relaxed без барьеров памяти? Один поток может никогда не увидеть изменения сделанное в другом потоке, а это значит, что такой код будет корректно работать только если процессор имеет одно ядро, т.е. код не универсален. А какой тогда смысл использовать atomic если volatile имеет примерно такие же характеристики?
И каждый день — без права на ошибку...
Re[4]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, Abyx, Вы писали:
A>всё так, но если у тебя процессор с 1 ядром, ты хочешь большего быстродействия (оптимизации программы) — то это повод использовать процессор хотя бы с 8 ядрами.
Я не хочу большего быстродействия; меня и текущее вполне устраивает. Я хотел избавится от потенциальной проблемы volatile и сделать код универсальным, но тогда получается, что я замедлю весь код.
И каждый день — без права на ошибку...
Re[5]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, B0FEE664, Вы писали:
BFE>А какой смысл в memory_order_relaxed без барьеров памяти? Один поток может никогда не увидеть изменения сделанное в другом потоке,
Какое-то страшно неверное понимание работы флага memory_order_relaxed. Его указание говорит об упорядоченности относительно других операций с памятью, а не об видимости изменений. С видимостью никаких проблем нет.
Если один поток должен передать данные другому, то достаточно использовать memory_order_relaxed, если все данные содержатся в самом atomic (например, atomic<int> содержит в себе возвращаемое значение функции), или memory_order_release, если данные содержатся в каком-то другом хранилище (например, когда поток записывает ответ в глобальный vector<int>, а потом устанавливает флаг atomic<bool> как признак готовности результата).
BFE> А какой тогда смысл использовать atomic если volatile имеет примерно такие же характеристики?
Они служат для разных целей!
Ну и если уж говорить про memory_order_relaxed, то как ты думаешь, какими гарантиями на упорядоченность обладают операции с volatile переменными? Там ситуация даже хуже чем с atomic+memory_order_relaxed. Если не используются расширения компилятора (вроде упомянутого по ссылке Abyx /volatie:ms), то volatile доступ лишь не перемешивается с доступом к другим volatile-переменным, но при этом не вводит никаких барьеров и свободно перемешивается с другими записями/чтениями из памяти. Вот из-за этого и нужно для синхронизации потоков использовать atimic, а не volatile — c atomic можно написать корректный переносимый алгоритм, а с volatile, кроме совсем тривиальных случаев, — нет.
Re[5]: volatile bool vs std::atomic_flag vs std::atomic<bool>
BFE>А какой смысл в memory_order_relaxed без барьеров памяти? Один поток может никогда не увидеть изменения сделанное в другом потоке, а это значит, что такой код будет корректно работать только если процессор имеет одно ядро, т.е. код не универсален.
Очень странные представления о том, как происходит запись в память. Почему изменения не будут видимы? Relaxed означает что процессор может переупорядочить запись в эту переменную с записью в другие переменные, другие флаги запрещают определенные переупорядочивания.
Мало того, так как на x86 сохранения не переупорядочиваются с другими сохранениями а загрузки не переупорядочиваются с другими загрузками, то компилятор не будет в релизе вставлять барьеры и префиксы lock вообще, если ты используешь что-то слабее memory_order_seq_cst.
BFE>А какой тогда смысл использовать atomic если volatile имеет примерно такие же характеристики?
Это аннотация для читателя кода. Может memory_order_acquire(release) не будут генерировать барьеры на той архитектуре, под которую я пишу, тем не менее, когда я увижу использование этих флагов я сразу пойму, что имеется ввиду read acquire/write release, т.е. перед сохранением с release где-то записывается значение а после загрузки с acquire оно может считываться. Т.е. требуется определенный порядок без переупорядочивания загрузок/сохранений, код зависит от этого порядка и он обозначен явно. А если я вижу volatile я понимаю ничего, может это нужно чтобы обойти какую-нибудь багу, вызванную слишком агрессивными оптимизациями в компиляторе?
Re[6]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, watchmaker, Вы писали:
BFE>>А какой смысл в memory_order_relaxed без барьеров памяти? Один поток может никогда не увидеть изменения сделанное в другом потоке,
W>Какое-то страшно неверное понимание работы флага memory_order_relaxed. Его указание говорит об упорядоченности относительно других операций с памятью, а не об видимости изменений. С видимостью никаких проблем нет.
Откуда следует, что нет никаких проблем с видимостью? Подозреваю, что из практики, а не из стандарта.
W>Если один поток должен передать данные другому, то достаточно использовать memory_order_relaxed,
Откуда это следует?
BFE>> А какой тогда смысл использовать atomic если volatile имеет примерно такие же характеристики? W>Они служат для разных целей!
Созданы они для других целей, но это не значит, что их нельзя использовать.
W>Ну и если уж говорить про memory_order_relaxed, то как ты думаешь, какими гарантиями на упорядоченность обладают операции с volatile переменными?
Никакими. Но что, если мне никакие гарантии упорядоченности не нужны?
И каждый день — без права на ошибку...
Re[7]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, B0FEE664, Вы писали:
W>>Какое-то страшно неверное понимание работы флага memory_order_relaxed. Его указание говорит об упорядоченности относительно других операций с памятью, а не об видимости изменений. С видимостью никаких проблем нет. BFE>Откуда следует, что нет никаких проблем с видимостью? Подозреваю, что из практики, а не из стандарта.
Подозреваешь? И слышал о стандарте? Тогда что же тебя удерживает от того, чтобы открыть его и избавится от подозрений?
§1.10/28
W>>Если один поток должен передать данные другому, то достаточно использовать memory_order_relaxed, BFE>Откуда это следует?
Не надо вырывать из предложения часть.
BFE>>> А какой тогда смысл использовать atomic если volatile имеет примерно такие же характеристики? W>>Они служат для разных целей! BFE>Созданы они для других целей, но это не значит, что их нельзя использовать.
Безусловно. Только область использования по сравнению с atomic оказывается крайне ограниченной — множество вещей, возможные с atomic, просто не реализуются через volatile.
Попробуй, например, реализовать уже упомянутый сценарий: поток А заполняет глобальный вектор данными и сигнализирует ждущему потоку Б о готовности результата; после чего поток Б, например, выводит в stdout его содержимое. С atomic такая логика реализуется элементарно. Теперь сделай это с volatile.
BFE>Никакими. Но что, если мне никакие гарантии упорядоченности не нужны?
Тогда memory_order_relaxed — твой выбор.
Re[6]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, ELazin, Вы писали:
BFE>>А какой смысл в memory_order_relaxed без барьеров памяти? Один поток может никогда не увидеть изменения сделанное в другом потоке, а это значит, что такой код будет корректно работать только если процессор имеет одно ядро, т.е. код не универсален. EL>Очень странные представления о том, как происходит запись в память. Почему изменения не будут видимы? Relaxed означает что процессор может переупорядочить запись в эту переменную с записью в другие переменные, другие флаги запрещают определенные переупорядочивания.
Для atomic объектов гарантируются три различные характеристики: атомарность, видимость и упорядоченность. Так вот, конкретно для relaxed операций нет никаких гарантий упорядоченности, а это значит, что их видимость обеспечивается только расплывчатой фразой про конечный период времени:
An implementation should ensure that the last value (in modification order) assigned by an atomic or
synchronization operation will become visible to all other threads in a finite period of time.
Более того, в примечании 1.10/5 прямо сказано, что операции над relaxed объектами не являются синхронизирующими операциями. А это, по моему мнению, означает ровно одно: две нитки могут видеть разные значения одной и той же переменной, т.к. иначе на таких объектах можно было бы построить синхронизацию.
EL>Мало того, так как на x86 сохранения не переупорядочиваются с другими сохранениями а загрузки не переупорядочиваются с другими загрузками, то компилятор не будет в релизе вставлять барьеры и префиксы lock вообще, если ты используешь что-то слабее memory_order_seq_cst.
У меня таргет платформа ARM, а не x86. (Замеры на ней дают схожие результаты по времени).
BFE>>А какой тогда смысл использовать atomic если volatile имеет примерно такие же характеристики? EL>Это аннотация для читателя кода.
Ну, есть ещё и комментарии.
И каждый день — без права на ошибку...
Re[8]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, watchmaker, Вы писали:
W>>>Какое-то страшно неверное понимание работы флага memory_order_relaxed. Его указание говорит об упорядоченности относительно других операций с памятью, а не об видимости изменений. С видимостью никаких проблем нет. BFE>>Откуда следует, что нет никаких проблем с видимостью? Подозреваю, что из практики, а не из стандарта. W>Подозреваешь?
Да. W>И слышал о стандарте?
И даже читал местами.
W> Тогда что же тебя удерживает от того, чтобы открыть его и избавится от подозрений? W>§1.10/28
Вот это загадочная фраза: "finite period of time". Сколько это? 2 секунды? 2 минуты? Год?
BFE>>Никакими. Но что, если мне никакие гарантии упорядоченности не нужны? W>Тогда memory_order_relaxed — твой выбор.
Это всё равно, что считать, что sizeof(int) равно 4. Сдаётся мне, что memory_order_relaxed ждёт та же плачевная участь...
И каждый день — без права на ошибку...
Re[9]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, B0FEE664, Вы писали:
BFE>Вот это загадочная фраза: "finite period of time". Сколько это? 2 секунды? 2 минуты? Год?
фраза загадочная, но стандарт никогда и не пытался зафиксировать какие-то ограничения по времени. максимум — это зафиксировать сложность STL алгоритмов в big-O нотации, причем в сложности учитываются лишь кол-во сравнений (а не вообще все операции)
сколько по стандарту может выполняться код
int i = 0;
++i;
минуту? год? почему бы и нет
смысл фразы "finite period of time" в том, что абстрактная машина должна все же что-то сделать магическое, чтобы новое значение переменной когда-то было увидено в другом потоке. ей запрещено ничего не делать по этому поводу
по поводу visibility и ordering тебе уже отвечали в соседних ветках несколько месяцев назад и ты даже Вильямса читал, но у тебя в голове барьер (гы-гы) какой-то. ты никак не можешь понять, что вещи хоть и взаимосвязанные, но разные. в языке нет средств для ускорения visibility, но есть средства получения предсказуемости при изменении нескольких ячеек памяти.
на пальцах:
если ты меняешь две переменные и как-то используешь ордеринг, то как только другой поток увидел новое значение у первой переменной, то он уже может полагаться на значение у второй переменной согласно описанным законам в стандарте. но при этом ты не знаешь когда же второй поток увидит новое значение у первой переменной (нет возможности влиять на visibility)
исключениями я бы назвал RMW операции (даже с relaxed order), где есть гарантия получить свежее значение (хотя опять не ясно через сколько лет)
Здравствуйте, uzhas, Вы писали:
BFE>>Вот это загадочная фраза: "finite period of time". Сколько это? 2 секунды? 2 минуты? Год? U>фраза загадочная, но стандарт никогда и не пытался зафиксировать какие-то ограничения по времени. максимум — это зафиксировать сложность STL алгоритмов в big-O нотации, причем в сложности учитываются лишь кол-во сравнений (а не вообще все операции) U>сколько по стандарту может выполняться код U>
U>int i = 0;
U>++i;
U>
U>минуту? год? почему бы и нет U>смысл фразы "finite period of time" в том, что абстрактная машина должна все же что-то сделать магическое, чтобы новое значение переменной когда-то было увидено в другом потоке. ей запрещено ничего не делать по этому поводу
Я согласен, что оперировать абсолютными значениями времени было бы глупо, однако никаких гарантий видимости эта фраза не даёт. Это не значит, что relaxed операции не нужны. relaxed операции нужны тогда, когда операции синхронизации программист берёт на себя и делает их с помощью других средств языка, например, с помощью барьеров.
U>по поводу visibility и ordering тебе уже отвечали в соседних ветках несколько месяцев назад и ты даже Вильямса читал, но у тебя в голове барьер (гы-гы) какой-то. ты никак не можешь понять, что вещи хоть и взаимосвязанные, но разные. в языке нет средств для ускорения visibility, но есть средства получения предсказуемости при изменении нескольких ячеек памяти.
Я понимаю, что visibility и ordering это разные, хотя и связанные понятия. Что такое "ускорение visibility" я не знаю, но только вот visibility к получению предсказуемости при изменении нескольких ячеек памяти имеет весьма опосредованное отношение.
U>на пальцах: U>если ты меняешь две переменные и как-то используешь ордеринг, то как только другой поток увидел новое значение у первой переменной, то он уже может полагаться на значение у второй переменной согласно описанным законам в стандарте. но при этом ты не знаешь когда же второй поток увидит новое значение у первой переменной (нет возможности влиять на visibility)
Есть явная гарантия получения "актуального" значения при использовании операций ведущих к синхронизации:
1.10/8
Certain library calls synchronize with other library calls performed by another thread. For example, an
atomic store-release synchronizes with a load-acquire that takes its value from the store.
U>исключениями я бы назвал RMW операции (даже с relaxed order), где есть гарантия получить свежее значение (хотя опять не ясно через сколько лет)
Нет такой гарантии.
Если в одном потоке есть вызов:
flag.clear(std::memory_order_relaxed);
а в другом:
do
{
}while(flag.test_and_set(std::memory_order_relaxed));
то гарантии, что второй поток когда-либо выйдет из цикла нет.
Здравствуйте, B0FEE664, Вы писали:
BFE>то гарантии, что второй поток когда-либо выйдет из цикла нет.
BFE>На практике такого не случается, однако это не значит, что такого не может быть согласно стандарту. Представьте себе платформу, где два процессора и где кэши этих процессоров согласуются специальными инструкциями, которые отсутствуют при relaxed операциях.
Не надо путать абстрактную машину C++ и реальное аппаратное обеспечение. Если рассмотреть такой компьютер, где для синхронизации нужны особые инструкции, то компилятор бы их всегда и использовал. Даже если в коде требуется лишь memory_order_relaxed. Использовал бы компилятор эти инструкции в частности из-за того, что в противном случае был бы нарушен тот самый пункт о конечности времени на распространение изменений.
Так что memory_order_relaxed ни к каким вечным циклам не приведёт. Просто на таком компьютере операции с memory_order_relaxed будут выполнятся примерно так же, как и с более строгими требованиями — через эти самые специальные инструкции по синхронизации кешей.
Re[11]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, B0FEE664, Вы писали:
BFE>Есть явная гарантия получения "актуального" значения при использовании операций ведущих к синхронизации: BFE>1.10/8 BFE>
BFE>Certain library calls synchronize with other library calls performed by another thread. For example, an
BFE>atomic store-release synchronizes with a load-acquire that takes its value from the store.
я не уверен, что твоя интерпретация этой цитаты корректна. надо мне вспомнить что такое "synchronize with"
U>>исключениями я бы назвал RMW операции (даже с relaxed order), где есть гарантия получить свежее значение (хотя опять не ясно через сколько лет) BFE>Нет такой гарантии.
так вот же стандарт:
29.3 Order and Consistency [atomics.order]
...
11 Atomic read-modify-write operations shall always read the last value (in the modification order) written
before the write associated with the read-modify-write operation.
Re[12]: volatile bool vs std::atomic_flag vs std::atomic<bool>
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, B0FEE664, Вы писали:
BFE>>Есть явная гарантия получения "актуального" значения при использовании операций ведущих к синхронизации: BFE>>1.10/8 BFE>>
BFE>>Certain library calls synchronize with other library calls performed by another thread. For example, an
BFE>>atomic store-release synchronizes with a load-acquire that takes its value from the store.
U>я не уверен, что твоя интерпретация этой цитаты корректна. надо мне вспомнить что такое "synchronize with"
In the absence of any additional synchronization, if one thread writes a value to ai then there is nothing that guarantees that another thread will see the value in any given time period. The standard specifies that it should be visible "in a reasonable period of time", but any given access may return a stale value.