Здравствуйте, 23W, Вы писали:
23W>Уже несколько раз читал в разных местах, что volatile директива перед переменной не гарантирует атомарность операции с самой переменной, при работе с ней в несколько потоков. Так ли это ? 23W>Действительно, разве изменение volatile bool или volatile BYTE переменной может быть не атомарной ? ведь это примитивный байтовый тип изменяемый за одну asm операцию. Или все же нужно менять содержимое таких переменных через Intelock???
23W>Прошу помощи.
volatile к атомарности не имеет вообще никакого отношения.
RB>В VS2005 volatile дополнительно к подавлению оптимизации работает как барьер соответствующей операции для самого компилятора (но не для процессора).
RB>Никакой атомарности для произвольных типов он не обеспечивает.
Для начала нужно в терминах разобраться, барьер суть примитив синхронизации потоков который позволяет заблокировать потоки в одной точке до завершения условия (например завершения потоков), а упорядоченный доступ к памяти это запрет на перестановку инструкций компилятором в целях оптимизации.
Здравствуйте, sysenter, Вы писали:
S>volatile нужен, чтобы компилятор не применял оптимизацию и только. Оптимизация подразумевает в том числе и перестановку ассемблерных инструкций.
+ проц не будет хранить значение volatile переменной в кеше, а только в памяти.
Здравствуйте, sysenter, Вы писали:
S>Здравствуйте, sysenter, Вы писали: S>>volatile нужен, чтобы компилятор не применял оптимизацию и только. Оптимизация подразумевает в том числе и перестановку ассемблерных инструкций. S>+ проц не будет хранить значение volatile переменной в кеше, а только в памяти.
а вот это уже явно — неправда.
Здравствуйте, sysenter, Вы писали:
S>Здравствуйте, rus blood, Вы писали:
RB>>В VS2005 volatile дополнительно к подавлению оптимизации работает как барьер соответствующей операции для самого компилятора (но не для процессора). RB>>Никакой атомарности для произвольных типов он не обеспечивает.
S>Откуда информация?
Здравствуйте, rus blood, Вы писали:
RB>На интеловских процессорах load/store операции с данными, размером меньше разрядности (<= 4 для x86 и <= 8 для x64) атомарны.
Здравствуйте, sysenter, Вы писали:
S>Если не выровнены, будет дополнительная операция чтения перед которой может вклиниться другой поток + падение производительности на чтении не выровненных данных.
Пентиум читает/пишет целыми кеш-лайнами. Которые могут быть и 8 байт, и 16, и 32 и даже 64, в зависимости от модели процессора.
Как данные выровнены внутри кеш-лайна, процессору все равно. Если данные пересекают границу кеш-лайна, насколько я понимаю, аппаратура не гарантирует атомарного обращения, чего бы не было написано в программе.
Здравствуйте, sysenter, Вы писали:
S>В других версиях VS атомарность volatile уже не обеспечивается?
да. представьте себе
из того что А тождественно Б, не означает что и Б тождественно А. (это так, пример из логики).
Здравствуйте, Pzz, Вы писали:
Pzz>volatile означает лишь одно: говорит компилятору, что к переменной может быть доступ помимо его "глаз". Это приводит к тому, что компилятор не может кешировать значение переменной в регистре, а вынужден генерировать обращение к памяти при каждом обращении.
Если быть технически точным, то говорит процу сбрасывать кэш линейку, соответствующую адресу в памяти переменной и брать значение из памяти при чтении. Так же запись будет производиться сразу в память вместо записи в кеш. Так же будет блокироваться шина у соответсвующей микросхемы памяти на время чтения/записи в памяти.
Здравствуйте, rus blood, Вы писали:
RB>2. Действительно, разве изменение volatile bool или volatile BYTE переменной может быть не атомарной ? RB>Если под словом "изменение" понимается одиночная store-операция, то не может.
АлёнаЦПП пишет:
Казалось бы уж что-что, так bool должен писаться в один прием. Я вычитала, что на некоторых Windows'ах это вовсе даже и не так. И атомарность присутствует только при работе с char
Здравствуйте, rus blood, Вы писали:
RB>Здравствуйте, 23W, Вы писали:
23W>>Здравствуйте, Pzz, Вы писали: Pzz>>>Вывод: используйте InterlockedXXX() или механизмы синхронизации.
23W>>Спасибо.
RB>Знаешь, почему такой срач возник? RB>Потому что ты не объяснил, какие именно операции ты выполняешь с переменными.
RB>Вот у тебя есть переменная, пусть даже volatile RB>
volatile long flag;
RB>Типичные операции с переменной RB>1. Запись значения RB>
flag = 1;
RB>2. Чтение значения RB>
long f = flag;
RB>3. Чтение/Изменение/Запись RB>
flag ++;
RB>В первых двух случаях имеется простые read и write операции, они атомарны, и никакой синхронизации сами по себе не требуют. RB>В последнем случае будет выполнены последовательно read и write операции, которые совместно — не атомарны. RB>Ни смотря на слово volatile, и на "волшебные" возможности MSVC2005, как некоторые считают.
RB>Для атомарности операций в последнем случае и предусмотрены interlocked-операции. RB>Все interlocked-операции производят read-modify-write последовательность действий (хотя в некоторых случаях шаг modify может отсутствовать). RB>В этом их отличие от простых read или write и предназначение.
Если несколько цепочек одновременно пишут в flag, разве операция 2 тоже атомарна ? Вернее не так, разве операция 2 прочитает некое валидное значение записанное одной из цепочек (последней), не будет ли там "микс" из нескольких значений.
Далее, если операция один всегда атомарна, накой в API реализовали InterlockedExchange() ??
P.S.: касательно операции 3. — volatile не разрешает делать ++ с переменной.
Здравствуйте, 23W, Вы писали:
.S>>+ проц не будет хранить значение volatile переменной в кеше, а только в памяти. 23W>а вот это уже явно — неправда.
Строго говоря, значение volatile переменной не будет храниться в регистре это факт.
Но, у ядер современных процов есть не разделяемые кеши и если хранить такую переменную в кеше, которая изменяется незаметно, то ядро просто этого не заметит. Логично предположить, что ядро каждый раз её из памяти читает.
Здравствуйте, sysenter, Вы писали:
S>Строго говоря, значение volatile переменной не будет храниться в регистре это факт. S>Но, у ядер современных процов есть не разделяемые кеши и если хранить такую переменную в кеше, которая изменяется незаметно, то ядро просто этого не заметит. Логично предположить, что ядро каждый раз её из памяти читает.
У тебя неверное представление об архитектуре интеловских процессоров.
Все, на самом деле, не так.
Здравствуйте, Jolly Roger, Вы писали:
Pzz>>В общем, если не хотите приключений на собственную ж..., лучше предохраняться использовать предназначенные для этого атомарные операции и/или примитивы синхронизации.
JR>Так проще, но не всегда лучше.
Ну скажем так. Множества людей, способных руками сделать лучше и людей, задающих наивные вопросы, почти не пересекаются
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, rus blood, Вы писали:
RB>>Это вовсе не тот случай. RB>>Это случай последовательных store-load по разным адресам, который как раз и может быть re-ordered процессором. RB>>И не надо было столько копий ломать, когда это явно сказано в мануале: RB>>8.2.3.4 Loads May Be Reordered with Earlier Stores to Different Locations
JR>А это из какого мануала?
У меня это набор скачанных с сайта документов.
Intel постоянно меняют название самого документа, как и состав разного набора документов по этой теме.
Поискал, сейчас это называется так.
Здравствуйте, 23W, Вы писали:
23W>Здравствуйте, Pzz, Вы писали: Pzz>>Вывод: используйте InterlockedXXX() или механизмы синхронизации.
23W>Спасибо.
Знаешь, почему такой срач возник?
Потому что ты не объяснил, какие именно операции ты выполняешь с переменными.
Вот у тебя есть переменная, пусть даже volatile
volatile long flag;
Типичные операции с переменной
1. Запись значения
flag = 1;
2. Чтение значения
long f = flag;
3. Чтение/Изменение/Запись
flag ++;
В первых двух случаях имеется простые read и write операции, они атомарны, и никакой синхронизации сами по себе не требуют.
В последнем случае будет выполнены последовательно read и write операции, которые совместно — не атомарны.
Ни смотря на слово volatile, и на "волшебные" возможности MSVC2005, как некоторые считают.
Для атомарности операций в последнем случае и предусмотрены interlocked-операции. Все interlocked-операции производят read-modify-write последовательность действий (хотя в некоторых случаях шаг modify может отсутствовать).
В этом их отличие от простых read или write и предназначение.
Здравствуйте, 23W, Вы писали:
23W>Здравствуйте, rus blood, Вы писали:
RB>>Здравствуйте, 23W, Вы писали:
RB>>>>Interlocked-операция на невыровненных данных может вообще не работать так, как ты от нее ожидаешь. 23W>>>ну вы меня вообще в тупик поставили, а как же тонны кода которые используют InterlockedIncrement() и подобное?
RB>>Они работают на выровненных данных. 23W>спасибо, но как я понял из ваших же фраз на выровненных данных они и не нужны как бы, там атомарность гарантируется архитектурой. 23W>Каков ваш конечный совет ? только конкретно пожалуйста.
Например, если InterlockedIncrement заменить на A++, то мы получим следующие действия процессора:
1) Загрузить А из памяти в регистр процессора.
2) Увеличить содержимое регистра на единицу.
3) Выгрузить новое значение из регистра в память.
Допустим, наша А выровнена на границу 4-х байт, и платформа обеспечивает атомарность операций с ней. Тогда каждая из 3-х операций будет атомарной, но вся последовательность таковой не будет, и другой поток может записать в А новое значение сразу после того, как наш поток выполнил пункт 1). Вот для таких случаев и нужны InterlockedIncrement() и т.п. функции. Другая альтернатива — синхронизацию доступа, но Interlocked-функции более предпочтительны с точки зрения производительности.
Зачем нужна volatile? Такой пример:
Допустим, у нас есть два потока, один из которых должен выполнять некоторую фоновую задачу, дожидаясь, пока другой поток завершит некую длительную операцию. Тогда мы можем использовать флаг, состояние которого будет периодически опрашивать первый поток, а второй поток его взведёт после завершения своей операции. Для такого флага и требуется использовать volatile. Разумеется, здесь тоже можно использовать InterlockedIncrement, но это излишество, так как Interlocked-операции на той-же x86 заметно более затратны, чем просто запись в память.
Здравствуйте, sysenter, Вы писали:
S>Строго говоря, значение volatile переменной не будет храниться в регистре это факт. S>Но, у ядер современных процов есть не разделяемые кеши и если хранить такую переменную в кеше, которая изменяется незаметно, то ядро просто этого не заметит. Логично предположить, что ядро каждый раз её из памяти читает.
Пентиум умеет синхронизировать кеши между процессорами/ядрами так, что программисту это незаметно. Это называется "когерентный кэш". И да, они между собой переговариваются, чтобы знать, когда синхронизировать кэш
Здравствуйте, 23W, Вы писали:
Pzz>>В общем, если не хотите приключений на собственную ж..., лучше предохраняться использовать предназначенные для этого атомарные операции и/или примитивы синхронизации. 23W>Скажите, как такое поведение процессора может сказаться на атомарности операции записи или чтения выровренных данных. Ведь два процессора одновременно писать в одну ячейку памяти не могут, только последовательно (шина то — одна).
В одном потоке вы пишете: flag = true; i = 5. Другой поток, на другом процессоре, может увидеть на мгновение, что i уже 5, а flag все еще false. Атомарности операций это не противоречит, но вот порядок исполнения — другой. А если то же самое делать через InterlockedXXX(), или под мьютексом, то порядок будет правильный, такой же, как в тексте.
On 06.09.2011 13:46, 23W wrote: > Уже несколько раз читал в разных местах, что volatile директива перед переменной > не гарантирует атомарность операции с самой переменной, при работе с ней в > несколько потоков. Так ли это ?
Так. volatile может быть объявлена любая переменная, любого типа.
Вообще, volatile -- это инструкция компилятору, чтобы он не делал
никаких предположений о текущем значении переменной для целей оптимизации.
Более ничего.
> Действительно, разве изменение volatile bool или volatile BYTE переменной может > быть не атомарной ?
Может.
ведь это примитивный байтовый тип изменяемый за одну asm > операцию.
Кто тебе это сказал ? Ссылку на стандарт даш ?
Или все же нужно менять содержимое таких переменных через Intelock???
Нужно. В смысле, нужно синхронизироваться, что такое Intelock я слабо представляю.
Здравствуйте, sysenter, Вы писали:
S>У VS есть нестандартное расширение трактовки volatile, это расширение гарантирует атомарность операции. В MSDN об этом написано.
Об этом же пишет многоуважаемая АленаЦэКрестКрест.
Здравствуйте, 23W, Вы писали:
23W>Т.е. я правильно понимаю что для данных размеров в байт (и это только тип char) можно не заморачиваться и считать что изменения volatile char будут атомарны. А вот при работе с двух-байтовыми данными (и более) нужна именно interlocked-операция, т.к. из-за невыровненности они могут быть неатомарны.
Interlocked-операция на невыровненных данных может вообще не работать так, как ты от нее ожидаешь.
Здравствуйте, sysenter, Вы писали:
S>АленаCPP утверждает, что в доке к VS 2005 написано про атомарность volatile.
Плевать на Алену.
В VS2005 volatile дополнительно к подавлению оптимизации работает как барьер соответствующей операции для самого компилятора (но не для процессора).
Никакой атомарности для произвольных типов он не обеспечивает.
Здравствуйте, sysenter, Вы писали:
S>Здравствуйте, 23W, Вы писали:
23W>>"Я нашла этот пункт только в документации к Visual Studio 2005. Я порылась на MSDN в поисках этого пункта в других версиях Visual Studio, не нашла."
S>Update 3.11.2006 S>volatile в исполнении Микрософт имеет Microsoft Specific пункт. А именно: атомарность операций гарантируется и, как следствие, использование в многопоточных программах приветствуется. Но код получается непортируемым, соответственно. S>Я нашла этот пункт только в документации к Visual Studio 2005
S>Ровно про это я и говорил.
а если глянуть двумя строками ниже, увидите то что я написал. Т.е. атомартность для volatile в VC++ гарантировалась на этапе Visual Studio 2005, потом эту строку из MSDN убрали!
Здравствуйте, 23W, Вы писали:
23W>а если глянуть двумя строками ниже, увидите то что я написал. Т.е. атомартность для volatile в VC++ гарантировалась на этапе Visual Studio 2005, потом эту строку из MSDN убрали!
Что не означает, что компилятор перестал гарантировать атомарность volatile или у вас есть другие сведения?
Здравствуйте, sysenter, Вы писали:
S>АлёнаЦПП пишет:
S>Казалось бы уж что-что, так bool должен писаться в один прием. Я вычитала, что на некоторых Windows'ах это вовсе даже и не так. И атомарность присутствует только при работе с char
А при чем тут Windows ?
ЗЫ
Вы сейчас очень сильно роняете авторитет Алены в моих глазах
Здравствуйте, 23W, Вы писали:
23W>1. простая операция чтения из переменной (1, 2, 4 байта) — атомарна.
да 23W>2. простая операция записи в такую же переменную — атомарна.
да 23W>3. сложные операторы и комбинации операций 1. и 2. — не атомарны и требуют вызова InterlockedXXX.
в многопоточных приложениях — да 23W>Причем операции 1 и 2 атомарны для 2 и 4 байтовых переменных если они выровнены (кстатий, вопрос — а если нет, что тогда?). Для однобайтных переменных операции 1 и 2 всегда атомарны.
да 23W>Атомарность операций 1 и 2 гарантируется архитектурой, а чтобы эта гарантия не была испорчена оптимизирующим компиляторам — нужна директива volatile переде переменными (и только для этого).
атомарность гарантируется архитектурой процессора, и компилятор и слово volatile здесь не причем.
Volatile только подавляет оптимизацию работы с переменной в компиляторе. Т.е., везде, где у тебя выполняются какие-то действия с такой переменной, компилятор будет честно вставлять операции чтения/записи в оперативную память, даже если у него под рукой будет регистр с значением этой переменной. У MSVC есть дополнительная плюшка — действие с volatile переменной (чтение или запись) является барьером для других операций такого же типа (чтение или запись). Т.е., компилятор не поменяет их местами.
23W>P.S.: как я уже спросил, — что делать если пемеренные 2 и 4х байтные и при этом не выровнены по адресу памяти (т.е. разделены границей кеш-линии).
Я не знаю.
Обычно переменные выровнены так, как надо.
Чтобы получить невыровеннные данные надо специально "целится себе в ногу".
Здравствуйте, sysenter, Вы писали:
S>>volatile нужен, чтобы компилятор не применял оптимизацию и только. Оптимизация подразумевает в том числе и перестановку ассемблерных инструкций.
S>+ проц не будет хранить значение volatile переменной в кеше, а только в памяти.
Здравствуйте, 23W, Вы писали:
23W>Суммируя то что сказали Jolly Roger и rus blood. 23W>Я понял что: 23W>1. простая операция чтения из переменной (1, 2, 4 байта) — атомарна. 23W>2. простая операция записи в такую же переменную — атомарна. 23W>3. сложные операторы и комбинации операций 1. и 2. — не атомарны и требуют вызова InterlockedXXX.
В общем и целом — да.
23W>Причем операции 1 и 2 атомарны для 2 и 4 байтовых переменных если они выровнены (кстатий, вопрос — а если нет, что тогда?).
Тогда для её записи или чтения процессору может потребоваться два обращения к шине.
23W>Атомарность операций 1 и 2 гарантируется архитектурой, а чтобы эта гарантия не была испорчена оптимизирующим компиляторам — нужна директива volatile переде переменными (и только для этого). 23W>Правильно ?
Не совсем. Компилятор может "испортить" не гарантию атомарности, она никуда не денется. Компилятор может просто сохранить значение переменной в регистре и использовать его(или вообще выкинуть обращение к данной переменной), и тогда изменения этой переменной другим потоком будут не видны данному потоку.
23W>P.S.: как я уже спросил, — что делать если пемеренные 2 и 4х байтные и при этом не выровнены по адресу памяти (т.е. разделены границей кеш-линии).
Кэш управляется процессором, он "прозрачен" для программы, поэтому в данном случае лучше вообще не брать во внимание наличие кэшей.
PS Вообще на RSDN на эту тему было очень много обсуждений , лучше воспользуйтесь поиском и внимательно прочитайте, это будет полезно.
Здравствуйте, 23W, Вы писали:
23W>Например такая переменная volatile LONG служит событием, где какой-либо поток проверяет ее на >0 чтобы начать обрабатывать данные, а другой поток взводит ее в 1 когда подготовил данные для первого потока. Изначально же эта переменная была 0.
Может получиться так, что глазами того потока, который проверяет, переменная уже >0, а данные еще "не приехали". А вот если бы "другой поток" сделал бы InterlockedIncrement(), так бы не получилось.
Сама по себе атомарность мало кому нужна. Нужен консистентный доступ к объемам памяти, несколько превышающим 4 байта
Здравствуйте, sysenter, Вы писали:
S>Про то, что ядра синхронизируют кеши я вкурсе. Как быть с DMA если он незаметно в оперативке поменяет значение volatile переменной или если эта переменная есть на самом деле участок памяти отображённый на устройство? Как проц узнает, что значение в памяти поменялось, если оно меняется при каждом считывании?
DMA не проходит мимо чипсета. Чипсет умеет общаться с процессором.
Если устройство отображается на память, есть 2 варианта: либо пометить соответствующие страницы, как некешируемые, либо програмно сбрасывать кеш до/после изменения данных, в зависимости от направления передачи.
Уже несколько раз читал в разных местах, что volatile директива перед переменной не гарантирует атомарность операции с самой переменной, при работе с ней в несколько потоков. Так ли это ?
Действительно, разве изменение volatile bool или volatile BYTE переменной может быть не атомарной ? ведь это примитивный байтовый тип изменяемый за одну asm операцию. Или все же нужно менять содержимое таких переменных через Intelock???
Для начала — компилятор имеет право вообще игнорировать модификатор.
А если не игнорирует, то должен отключать оптимизации при работе с этой переменной.
Что конкретно он должен делать и чего не должен — как я понимаю, не специфицировано.
Здравствуйте, breee breee, Вы писали:
BB>Здравствуйте, 23W, Вы писали:
23W>>Уже несколько раз читал в разных местах, что volatile директива перед переменной не гарантирует атомарность операции с самой переменной, при работе с ней в несколько потоков. Так ли это ? 23W>>Действительно, разве изменение volatile bool или volatile BYTE переменной может быть не атомарной ? ведь это примитивный байтовый тип изменяемый за одну asm операцию. Или все же нужно менять содержимое таких переменных через Intelock???
23W>>Прошу помощи.
BB>volatile к атомарности не имеет вообще никакого отношения.
т.е. изменение volatile bool переменной из нескольких цепочек ТРЕБУЕТ защиты ее через объекты синхронизации?
Здравствуйте, breee breee, Вы писали:
BB>Здравствуйте, 23W, Вы писали: 23W>>т.е. изменение volatile bool переменной из нескольких цепочек ТРЕБУЕТ защиты ее через объекты синхронизации? BB>Да.
У меня тут есть определенный скепсис, как может изменение volatile bool или volatile BYTE быть не атомарным? Вы могли бы доказать или дать ссылку на стандарт или авторитет по этой теме.
Я понимаю что volatile отключает оптимизацию, т.е. гарантируется постоянная проверка переменной. Но ведь операция с байтом должна быть примитивной (атомарной).
Здравствуйте, 23W, Вы писали:
23W>Уже несколько раз читал в разных местах, что volatile директива перед переменной не гарантирует атомарность операции с самой переменной, при работе с ней в несколько потоков. Так ли это ? 23W>Действительно, разве изменение volatile bool или volatile BYTE переменной может быть не атомарной ? ведь это примитивный байтовый тип изменяемый за одну asm операцию. Или все же нужно менять содержимое таких переменных через Intelock???
23W>Прошу помощи.
Почему она должна горантировать в С++ атомарность.
Не длжна просто оператор для того что бы эту переменную компелятор не поаптимизировал на случай если ты собираешься неведомымспособом для компилятора С++ модифицировать значение этой переменной.
Здравствуйте, 23W, Вы писали:
23W>У меня тут есть определенный скепсис, как может изменение volatile bool или volatile BYTE быть не атомарным? Вы могли бы доказать или дать ссылку на стандарт или авторитет по этой теме. 23W>Я понимаю что volatile отключает оптимизацию, т.е. гарантируется постоянная проверка переменной. Но ведь операция с байтом должна быть примитивной (атомарной).
На интеловских процессорах load/store операции с данными, размером меньше разрядности (<= 4 для x86 и <= 8 для x64) атомарны.
Здравствуйте, 23W, Вы писали:
23W>Здравствуйте, breee breee, Вы писали:
BB>>Здравствуйте, 23W, Вы писали: 23W>>>т.е. изменение volatile bool переменной из нескольких цепочек ТРЕБУЕТ защиты ее через объекты синхронизации? BB>>Да.
23W>У меня тут есть определенный скепсис, как может изменение volatile bool или volatile BYTE быть не атомарным? Вы могли бы доказать или дать ссылку на стандарт или авторитет по этой теме.
изменение будет атомарным в пределах одного ядра. В случае нескольких ядер обязательно нужно использовать memory barrier, которые достигаются с помощью interlocked операций.
Здравствуйте, mike_rs, Вы писали:
_>изменение будет атомарным в пределах одного ядра. В случае нескольких ядер обязательно нужно использовать memory barrier, которые достигаются с помощью interlocked операций.
В случае нескольких ядер отдельная load или store операция с данными размером в байт также будет атомарна.
Данные большего размера (2, 4, 8 на x64) байт обычно выровнены, поэтому операции с ними также будут атомарны.
Барьеры используются для обеспечения правильного порядка выполнения и видимости на других ядрах последовательных операций load/store.
Т.е., когда последовательно идут load-load, load-store, store-store или store-load операции.
Если у вас выполняется один load (или один store) из переменной volatile, где вы тут собираетесь барьеры вставлять?
Барьеры реализуются не только с помощью interlocked-операций, есть специальные команды процессора.
То, что interlocked-операция работает в качестве барьера, может зависеть от реализации процессора ("расслабленности").
Здравствуйте, 23W, Вы писали:
23W>Здравствуйте, breee breee, Вы писали:
BB>>Здравствуйте, 23W, Вы писали: 23W>>>т.е. изменение volatile bool переменной из нескольких цепочек ТРЕБУЕТ защиты ее через объекты синхронизации? BB>>Да.
23W>У меня тут есть определенный скепсис, как может изменение volatile bool или volatile BYTE быть не атомарным? Вы могли бы доказать или дать ссылку на стандарт или авторитет по этой теме.
Вам известны все существующие и будущие архитектуры процессоров, чтобы быть в этом увереным? Ссылку на стандарт дать невозможно, т.к. в текущем стандарте (2003 года) нет ничего про многопоточность, атомарность и т.д., поэтому мы должны предполагать худший случай.
23W>Я понимаю что volatile отключает оптимизацию, т.е. гарантируется постоянная проверка переменной.
Как сказали выше, есть еще проблема с видимостью изменения переменной между ядрами. Синхронизация обеспечивает эту видимость.
23W>Но ведь операция с байтом должна быть примитивной (атомарной).
Откуда эта информация?
Здравствуйте, breee breee, Вы писали:
BB>Здравствуйте, 23W, Вы писали:
23W>>Но ведь операция с байтом должна быть примитивной (атомарной). BB>Откуда эта информация?
rus blood подтвердил (для x86 и x64 платформ).
Здравствуйте, rus blood, Вы писали:
RB>Здравствуйте, mike_rs, Вы писали:
_>>изменение будет атомарным в пределах одного ядра. В случае нескольких ядер обязательно нужно использовать memory barrier, которые достигаются с помощью interlocked операций.
RB>В случае нескольких ядер отдельная load или store операция с данными размером в байт также будет атомарна. RB>Данные большего размера (2, 4, 8 на x64) байт обычно выровнены, поэтому операции с ними также будут атомарны.
RB>Барьеры используются для обеспечения правильного порядка выполнения и видимости на других ядрах последовательных операций load/store. RB>Т.е., когда последовательно идут load-load, load-store, store-store или store-load операции. RB>Если у вас выполняется один load (или один store) из переменной volatile, где вы тут собираетесь барьеры вставлять?
RB>Барьеры реализуются не только с помощью interlocked-операций, есть специальные команды процессора. RB>То, что interlocked-операция работает в качестве барьера, может зависеть от реализации процессора ("расслабленности").
Т.е. я правильно понимаю что для данных размеров в байт (и это только тип char) можно не заморачиваться и считать что изменения volatile char будут атомарны. А вот при работе с двух-байтовыми данными (и более) нужна именно interlocked-операция, т.к. из-за невыровненности они могут быть неатомарны.
Здравствуйте, breee breee, Вы писали:
23W>>т.е. изменение volatile bool переменной из нескольких цепочек ТРЕБУЕТ защиты ее через объекты синхронизации? BB>Да.
У VS есть нестандартное расширение трактовки volatile, это расширение гарантирует атомарность операции. В MSDN об этом написано.
Здравствуйте, sysenter, Вы писали:
S>Здравствуйте, breee breee, Вы писали:
23W>>>т.е. изменение volatile bool переменной из нескольких цепочек ТРЕБУЕТ защиты ее через объекты синхронизации? BB>>Да.
S>У VS есть нестандартное расширение трактовки volatile, это расширение гарантирует атомарность операции. В MSDN об этом написано.
Вот это место из MSDN (http://msdn.microsoft.com/ru-ru/library/12a04hfd.aspx)
Objects declared as volatile are not used in certain optimizations because their values can change at any time. The system always reads the current value of a volatile object at the point it is requested, even if a previous instruction asked for a value from the same object. Also, the value of the object is written immediately on assignment.
Also, when optimizing, the compiler must maintain ordering among references to volatile objects as well as references to other global objects. In particular,
A write to a volatile object (volatile write) has Release semantics; a reference to a global or static object that occurs before a write to a volatile object in the instruction sequence will occur before that volatile write in the compiled binary.
A read of a volatile object (volatile read) has Acquire semantics; a reference to a global or static object that occurs after a read of volatile memory in the instruction sequence will occur after that volatile read in the compiled binary.
This allows volatile objects to be used for memory locks and releases in multithreaded applications.
Здравствуйте, 23W, Вы писали:
23W>Здравствуйте, breee breee, Вы писали:
BB>>Здравствуйте, 23W, Вы писали:
23W>>>Но ведь операция с байтом должна быть примитивной (атомарной). BB>>Откуда эта информация? 23W>rus blood подтвердил (для x86 и x64 платформ).
Это подфорум о чистом C++. Если конкретная платформа и компилятор обеспечивают атомарность, это еще не значит, что на это можно полагаться в общем случае.
Кроме того для x86 и x64, синхронизация все еще требуется для обеспечения видимости, насколько я понимаю.
Здравствуйте, sysenter, Вы писали:
S>Здравствуйте, sysenter, Вы писали:
S>>У VS есть нестандартное расширение трактовки volatile, это расширение гарантирует атомарность операции. В MSDN об этом написано.
S>Об этом же пишет многоуважаемая АленаЦэКрестКрест.
Вот ее фраза если точно:
"Я нашла этот пункт только в документации к Visual Studio 2005. Я порылась на MSDN в поисках этого пункта в других версиях Visual Studio, не нашла."
Здравствуйте, 23W, Вы писали:
23W>"Я нашла этот пункт только в документации к Visual Studio 2005. Я порылась на MSDN в поисках этого пункта в других версиях Visual Studio, не нашла."
Update 3.11.2006
volatile в исполнении Микрософт имеет Microsoft Specific пункт. А именно: атомарность операций гарантируется и, как следствие, использование в многопоточных программах приветствуется. Но код получается непортируемым, соответственно.
Я нашла этот пункт только в документации к Visual Studio 2005
Здравствуйте, rus blood, Вы писали:
RB>Здравствуйте, 23W, Вы писали:
23W>>Т.е. я правильно понимаю что для данных размеров в байт (и это только тип char) можно не заморачиваться и считать что изменения volatile char будут атомарны. А вот при работе с двух-байтовыми данными (и более) нужна именно interlocked-операция, т.к. из-за невыровненности они могут быть неатомарны.
RB>Interlocked-операция на невыровненных данных может вообще не работать так, как ты от нее ожидаешь.
ну вы меня вообще в тупик поставили, а как же тонны кода которые используют InterlockedIncrement() и подобное?
Здравствуйте, 23W, Вы писали:
RB>>Interlocked-операция на невыровненных данных может вообще не работать так, как ты от нее ожидаешь. 23W>ну вы меня вообще в тупик поставили, а как же тонны кода которые используют InterlockedIncrement() и подобное?
Здравствуйте, rus blood, Вы писали:
RB>Здравствуйте, 23W, Вы писали:
RB>>>Interlocked-операция на невыровненных данных может вообще не работать так, как ты от нее ожидаешь. 23W>>ну вы меня вообще в тупик поставили, а как же тонны кода которые используют InterlockedIncrement() и подобное?
RB>Они работают на выровненных данных.
спасибо, но как я понял из ваших же фраз на выровненных данных они и не нужны как бы, там атомарность гарантируется архитектурой.
Каков ваш конечный совет ? только конкретно пожалуйста.
Здравствуйте, rus blood, Вы писали:
RB>В VS2005 volatile дополнительно к подавлению оптимизации работает как барьер соответствующей операции для самого компилятора (но не для процессора). RB>Никакой атомарности для произвольных типов он не обеспечивает.
Откуда информация? В других версиях VS атомарность volatile уже не обеспечивается?
Здравствуйте, 23W, Вы писали:
23W>Действительно, разве изменение volatile bool или volatile BYTE переменной может быть не атомарной ? ведь это примитивный байтовый тип изменяемый за одну asm операцию. Или все же нужно менять содержимое таких переменных через Intelock???
1. То, что в Си выглядит одной строкой, в ассемблере может быть несколькими командами. Которые, естественно, не атомарны
2. Процессор может обращаться к памяти не в том порядке, в котором обращения к памяти записаны в программе. Глядя из другого потока, это может быть заметно.
volatile означает лишь одно: говорит компилятору, что к переменной может быть доступ помимо его "глаз". Это приводит к тому, что компилятор не может кешировать значение переменной в регистре, а вынужден генерировать обращение к памяти при каждом обращении.
Вывод: используйте InterlockedXXX() или механизмы синхронизации.
Здравствуйте, 23W, Вы писали:
23W>Каков ваш конечный совет ? только конкретно пожалуйста.
Совет в чем?
Вот ваши вопросы
1. Уже несколько раз читал в разных местах, что volatile директива перед переменной не гарантирует атомарность операции с самой переменной, при работе с ней в несколько потоков. Так ли это ?
В общем случае, да, так.
Например, на x86 любые load/store операции с переменной типа __int64 будут неатомарны.
2. Действительно, разве изменение volatile bool или volatile BYTE переменной может быть не атомарной ?
Если под словом "изменение" понимается одиночная store-операция, то не может.
3. Или все же нужно менять содержимое таких переменных через Intelock???
Необязательно.
Здравствуйте, sysenter, Вы писали:
S>Если быть технически точным, то говорит процу сбрасывать кэш линейку, соответствующую адресу в памяти переменной и брать значение из памяти при чтении. Так же запись будет производиться сразу в память вместо записи в кеш. Так же будет блокироваться шина у соответсвующей микросхемы памяти на время чтения/записи в памяти.
Это interlocked так делает, а volatile — нет. В этом между ними разница.
Здравствуйте, rus blood, Вы писали:
RB>В последнем случае будет выполнены последовательно read и write операции, которые совместно — не атомарны. RB>Ни смотря на слово volatile, и на "волшебные" возможности MSVC2005, как некоторые считают.
Некоторые обоснованно считают, что компилятор может переставить местами инструкции, а при volatile компилятор (VS 2005) придерживается
исходного порядка.
Здравствуйте, sysenter, Вы писали:
S>Некоторые обоснованно считают, что компилятор может переставить местами инструкции, а при volatile компилятор (VS 2005) придерживается S>исходного порядка.
S>volatile makes the memory accesses ordered.
И что??
В VS2005 volatile дополнительно к подавлению оптимизации работает как барьер соответствующей операции для самого компилятора (но не для процессора). Никакой атомарности для произвольных типов он не обеспечивает.
Здравствуйте, rus blood, Вы писали:
RB>Какое это имеет отношение к теме атомарности? RB>Вообще, какая разница между RB>
flag++;
RB>и RB>
++flag;
RB>?
Ты изменяешь временный объект, который обратно в переменную не записывается т.е. вместо read/increment/write у тебя read/increment.
Чтение может произойти в регистр там же инкрементировано и записано в новую ячейку памяти или не записано вообще на усмотрение компилятора т.к. операция инкремента временного объекта который ничему не присваивается может быть отброшена компилятором.
Здравствуйте, 23W, Вы писали:
23W>Если несколько цепочек одновременно пишут в flag, разве операция 2 тоже атомарна ? Вернее не так, разве операция 2 прочитает некое валидное значение записанное одной из цепочек (последней), не будет ли там "микс" из нескольких значений.
Микса не будет.
И это и есть гарантия атомарности чтения данных.
На x86 это гарантия дается для 1,2,4-байтных выровненных данных.
23W>Далее, если операция один всегда атомарна, накой в API реализовали InterlockedExchange() ??
InterlockedExchange выполняет последовательность read-write.
Ведь одна возвращает исходное значение переменной, которое было на момент записи.
Простая последовательность
long oldValue = flag;
flag = newValue;
return oldValue;
атомарности не обеспечивает.
23W>P.S.: касательно операции 3. — volatile не разрешает делать ++ с переменной.
Чего?
Здравствуйте, sysenter, Вы писали:
S>Для начала нужно в терминах разобраться, барьер суть примитив синхронизации потоков который позволяет заблокировать потоки в одной точке до завершения условия (например завершения потоков), а упорядоченный доступ к памяти это запрет на перестановку инструкций компилятором в целях оптимизации.
Под барьером я имел в виду именно "барьер обращения к памяти".
Т.е., в твоих терминах — "запрет на перестановку инструкций".
И барьеры бывают как для компилятора, так и для процессора.
Вот для компилятора MSVC2005 слово volatile является барьером соответствующего типа.
В этом и есть специфика MSVC.
Здравствуйте, 23W, Вы писали:
23W>Здравствуйте, breee breee, Вы писали:
BB>>Здравствуйте, 23W, Вы писали: 23W>>>т.е. изменение volatile bool переменной из нескольких цепочек ТРЕБУЕТ защиты ее через объекты синхронизации? BB>>Да.
23W>У меня тут есть определенный скепсис, как может изменение volatile bool или volatile BYTE быть не атомарным? Вы могли бы доказать или дать ссылку на стандарт или авторитет по этой теме. 23W>Я понимаю что volatile отключает оптимизацию, т.е. гарантируется постоянная проверка переменной. Но ведь операция с байтом должна быть примитивной (атомарной).
Например, операция инкремента, выполняющаяся из двух разных потоков.
1) загрузка в регистр.
2) модификация.
3) сохранение.
В двух параллельных потоках загрузка может выполниться параллельно.
В результате, значение увеличится на 1 а не на 2.
volatile сам по себе не гарантирует, что инкремент пройдёт одним махом и с блокировкой ячейки.
volatile нужен совсем для другого.
например
while (global_flag){ ... }
не будет оптимизирован в вечный цикл, если global_flag объявлен с модификатором volatile,
даже если global_flag не используется внутри цикла.
Суммируя то что сказали Jolly Roger и rus blood.
Я понял что:
1. простая операция чтения из переменной (1, 2, 4 байта) — атомарна.
2. простая операция записи в такую же переменную — атомарна.
3. сложные операторы и комбинации операций 1. и 2. — не атомарны и требуют вызова InterlockedXXX.
Причем операции 1 и 2 атомарны для 2 и 4 байтовых переменных если они выровнены (кстатий, вопрос — а если нет, что тогда?). Для однобайтных переменных операции 1 и 2 всегда атомарны.
Атомарность операций 1 и 2 гарантируется архитектурой, а чтобы эта гарантия не была испорчена оптимизирующим компиляторам — нужна директива volatile переде переменными (и только для этого).
Правильно ?
P.S.: как я уже спросил, — что делать если пемеренные 2 и 4х байтные и при этом не выровнены по адресу памяти (т.е. разделены границей кеш-линии).
P.P.S: rus blood, я извиняюсь за оператор ++ и volatile — был неправ!
Здравствуйте, 23W, Вы писали:
23W>Атомарность операций 1 и 2 гарантируется архитектурой, а чтобы эта гарантия не была испорчена оптимизирующим компиляторам — нужна директива volatile переде переменными (и только для этого). 23W>Правильно ?
Отцы, вы забываете про то, что процессор может изменить порядок фактического обращения к памяти — по сравнению с ассемблерным кодом, а даже не сишным. При этом если "смотреть" с одного ядра, то для x86-х процессоров гарантируется, что вы этого не заметите. А вот если смотреть на ту же память с соседнего ядра, то можете и заметить.
В общем, если не хотите приключений на собственную ж..., лучше предохраняться использовать предназначенные для этого атомарные операции и/или примитивы синхронизации.
Здравствуйте, 23W, Вы писали:
23W>Причем операции 1 и 2 атомарны для 2 и 4 байтовых переменных если они выровнены (кстатий, вопрос — а если нет, что тогда?). Для однобайтных переменных операции 1 и 2 всегда атомарны.
Если не выровнены, будет дополнительная операция чтения перед которой может вклиниться другой поток + падение производительности на чтении не выровненных данных.
23W>Атомарность операций 1 и 2 гарантируется архитектурой, а чтобы эта гарантия не была испорчена оптимизирующим компиляторам — нужна директива volatile переде переменными (и только для этого). 23W>Правильно ?
volatile нужен, чтобы компилятор не применял оптимизацию и только. Оптимизация подразумевает в том числе и перестановку ассемблерных инструкций.
23W>P.S.: как я уже спросил, — что делать если пемеренные 2 и 4х байтные и при этом не выровнены по адресу памяти (т.е. разделены границей кеш-линии).
Компилятор сам обычно выравнивает, но это можно отключить.
Здравствуйте, Pzz, Вы писали:
Pzz>Здравствуйте, 23W, Вы писали:
23W>>Атомарность операций 1 и 2 гарантируется архитектурой, а чтобы эта гарантия не была испорчена оптимизирующим компиляторам — нужна директива volatile переде переменными (и только для этого). 23W>>Правильно ?
Pzz>Отцы, вы забываете про то, что процессор может изменить порядок фактического обращения к памяти — по сравнению с ассемблерным кодом, а даже не сишным. При этом если "смотреть" с одного ядра, то для x86-х процессоров гарантируется, что вы этого не заметите. А вот если смотреть на ту же память с соседнего ядра, то можете и заметить.
Pzz>В общем, если не хотите приключений на собственную ж..., лучше предохраняться использовать предназначенные для этого атомарные операции и/или примитивы синхронизации.
Скажите, как такое поведение процессора может сказаться на атомарности операции записи или чтения выровренных данных. Ведь два процессора одновременно писать в одну ячейку памяти не могут, только последовательно (шина то — одна).
Здравствуйте, Pzz, Вы писали:
Pzz>Пентиум читает/пишет целыми кеш-лайнами. Которые могут быть и 8 байт, и 16, и 32 и даже 64, в зависимости от модели процессора.
В кеш проц может блоками прочитать из памяти т.к. принцип сосредоточенности во времени никто не отменял и именно так и происходит.
Мы же говорим про атомарность операций над простыми данными размером — 2 или 4 байта. Операции над ними тот же инкремент происходят в регистре, если данные не выровнены в регистр поместить данные за одно обращение нельзя.
Или нет? Отчего тогда падение производительности на операциях с не выровненными данными?
Здравствуйте, Pzz, Вы писали:
Pzz>Здравствуйте, 23W, Вы писали:
23W>>Атомарность операций 1 и 2 гарантируется архитектурой, а чтобы эта гарантия не была испорчена оптимизирующим компиляторам — нужна директива volatile переде переменными (и только для этого). 23W>>Правильно ?
Pzz>Отцы, вы забываете про то, что процессор может изменить порядок фактического обращения к памяти — по сравнению с ассемблерным кодом, а даже не сишным. При этом если "смотреть" с одного ядра, то для x86-х процессоров гарантируется, что вы этого не заметите. А вот если смотреть на ту же память с соседнего ядра, то можете и заметить.
Да, но это уже другой вопрос Для его решения существуют барьеры, volatile тут ни при чём
Pzz>В общем, если не хотите приключений на собственную ж..., лучше предохраняться использовать предназначенные для этого атомарные операции и/или примитивы синхронизации.
Здравствуйте, rus blood, Вы писали:
RB>Volatile только подавляет оптимизацию работы с переменной в компиляторе. Т.е., везде, где у тебя выполняются какие-то действия с такой переменной, компилятор будет честно вставлять операции чтения/записи в оперативную память, даже если у него под рукой будет регистр с значением этой переменной. У MSVC есть дополнительная плюшка — действие с volatile переменной (чтение или запись) является барьером для других операций такого же типа (чтение или запись). Т.е., компилятор не поменяет их местами.
Любой вменяемый компилятор обладает этой "плюшкой".
И кстати, в отношении любых нелокальных переменных (а не только volatile) любой вызов не-inline функции обладает тем же эффектом: компилятор же не может знать, имеет эта функция доступ к этим переменным, или не имеет, и поэтому предполагает худшее. Благодаря этому данные, защищенные mutex'ом, не обязательно делать volatile: вызовы функций, захватывающих/освобождающих mutex заодно заставят компилятор упорядочить обращения к данным.
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, Pzz, Вы писали:
Pzz>>Здравствуйте, 23W, Вы писали:
23W>>>Атомарность операций 1 и 2 гарантируется архитектурой, а чтобы эта гарантия не была испорчена оптимизирующим компиляторам — нужна директива volatile переде переменными (и только для этого). 23W>>>Правильно ?
Pzz>>Отцы, вы забываете про то, что процессор может изменить порядок фактического обращения к памяти — по сравнению с ассемблерным кодом, а даже не сишным. При этом если "смотреть" с одного ядра, то для x86-х процессоров гарантируется, что вы этого не заметите. А вот если смотреть на ту же память с соседнего ядра, то можете и заметить.
JR>Да, но это уже другой вопрос Для его решения существуют барьеры, volatile тут ни при чём
Да нет же, это все тот же вопрос! Как изменение порядка фактического обращения к памяти может разрушить атомарность простых операций чтения и записи для выровненных данных ? Это важно!
И если проц. эту атомарность разрушает — как именно, конкретно с этим бороться! Какие функции API, какие приемы ?
Pzz>>В общем, если не хотите приключений на собственную ж..., лучше предохраняться использовать предназначенные для этого атомарные операции и/или примитивы синхронизации. JR>Так проще, но не всегда лучше.
Дайте лучшее решение.
Здравствуйте, rus blood, Вы писали:
RB>Volatile только подавляет оптимизацию работы с переменной в компиляторе. Т.е., везде, где у тебя выполняются какие-то действия с такой переменной, компилятор будет честно вставлять операции чтения/записи в оперативную память, даже если у него под рукой будет регистр с значением этой переменной.
Подожди, вот я написал, что проц не хранит volatile переменную в неразделяемом кеше, а ты мне написал что я не разбираюсь в архитектуре процов intel. Теперь сам пишешь, что операции чтения/записи с volatile переменными происходят в оперативной памяти. Так где же правда?
Здравствуйте, sysenter, Вы писали:
S>Если DMA изменит значение volatile переменной, а она у одного ядра закешированна в не разделяемом кеше L1, как это разруливается?
Вам нужно понять одну простую вещь.
Процессор, ядра, кеш, и прочее железо "понятия не имеют" о слове volatile.
На участках памяти, которые выделяются под переменные, слово volatile никак не влияет.
Здравствуйте, rus blood, Вы писали:
RB>Вам нужно понять одну простую вещь. RB>Процессор, ядра, кеш, и прочее железо "понятия не имеют" о слове volatile. RB>На участках памяти, которые выделяются под переменные, слово volatile никак не влияет.
Здравствуйте, Pzz, Вы писали:
Pzz>Здравствуйте, 23W, Вы писали:
Pzz>>>В общем, если не хотите приключений на собственную ж..., лучше предохраняться использовать предназначенные для этого атомарные операции и/или примитивы синхронизации. 23W>>Скажите, как такое поведение процессора может сказаться на атомарности операции записи или чтения выровренных данных. Ведь два процессора одновременно писать в одну ячейку памяти не могут, только последовательно (шина то — одна).
Pzz>В одном потоке вы пишете: flag = true; i = 5. Другой поток, на другом процессоре, может увидеть на мгновение, что i уже 5, а flag все еще false. Атомарности операций это не противоречит, но вот порядок исполнения — другой. А если то же самое делать через InterlockedXXX(), или под мьютексом, то порядок будет правильный, такой же, как в тексте.
Спасибо, это понятно. Я пока говорил только о использовании одной переменной (в качестве флаг, события, симофора, мьютекса или другого), пока пришел к выводу что volatile bool или volatile LONG и т.п. могут так использоваться (до тех пор пока компилятор выравнивает стуркутры внутри кода на границы в 4 или 8 байт, зависит от x86 или x64). Для этого к ним могу применятся только примитивные операции чтения или записи.
Например такая переменная volatile LONG служит событием, где какой-либо поток проверяет ее на >0 чтобы начать обрабатывать данные, а другой поток взводит ее в 1 когда подготовил данные для первого потока. Изначально же эта переменная была 0.
Здравствуйте, Pzz, Вы писали:
Pzz>В одном потоке вы пишете: flag = true; i = 5. Другой поток, на другом процессоре, может увидеть на мгновение, что i уже 5, а flag все еще false. Атомарности операций это не противоречит, но вот порядок исполнения — другой. А если то же самое делать через InterlockedXXX(), или под мьютексом, то порядок будет правильный, такой же, как в тексте.
1. ТС спрашивает про атомарность, а не про синхронизацию или порядок работы с памятью.
2. То, что interlocked-операция работает как барьер — это фича реализации процессора ("расслабленность").
Вполне могут быть архитектуры, где это не так, и требуются "настоящие" барьеры.
3. Интеловские процессоры достаточно "строгие" в смысле переупорядочивания.
В частности, твой пример flag=true; i=5; будет работать корректно — все другие ядра "увидят" изменения в правильном порядке.
Здравствуйте, Pzz, Вы писали:
Pzz>Пентиум умеет синхронизировать кеши между процессорами/ядрами так, что программисту это незаметно. Это называется "когерентный кэш". И да, они между собой переговариваются, чтобы знать, когда синхронизировать кэш
Про то, что ядра синхронизируют кеши я вкурсе. Как быть с DMA если он незаметно в оперативке поменяет значение volatile переменной или если эта переменная есть на самом деле участок памяти отображённый на устройство? Как проц узнает, что значение в памяти поменялось, если оно меняется при каждом считывании?
Здравствуйте, sysenter, Вы писали:
RB>>Volatile только подавляет оптимизацию работы с переменной в компиляторе. Т.е., везде, где у тебя выполняются какие-то действия с такой переменной, компилятор будет честно вставлять операции чтения/записи в оперативную память, даже если у него под рукой будет регистр с значением этой переменной.
S>Подожди, вот я написал, что проц не хранит volatile переменную в неразделяемом кеше, а ты мне написал что я не разбираюсь в архитектуре процов intel. Теперь сам пишешь, что операции чтения/записи с volatile переменными происходят в оперативной памяти. Так где же правда?
Правда — в силе.
Ты пишешь про хранение значений volatile-переменных в кеше процессора.
Я говорю про генерацию кода компилятором.
Здравствуйте, sysenter, Вы писали:
Pzz>>Пентиум читает/пишет целыми кеш-лайнами. Которые могут быть и 8 байт, и 16, и 32 и даже 64, в зависимости от модели процессора.
S>В кеш проц может блоками прочитать из памяти т.к. принцип сосредоточенности во времени никто не отменял и именно так и происходит.
Че такое "принцип сосредоточенности во времени"?
S>Мы же говорим про атомарность операций над простыми данными размером — 2 или 4 байта. Операции над ними тот же инкремент происходят в регистре, если данные не выровнены в регистр поместить данные за одно обращение нельзя.
Аппаратура работает блоками размера N. Если данные не выровненны на границу блока, то да, одним обращением не обойдешься. N разный в зависимости от модели процессора и от места в нем (т.е., для работы с внешней памятью и для пересылок по внутренней шине N разный). Но я не уверен, что в современном процессоре есть хоть одно место, где N равен 4
S>Или нет? Отчего тогда падение производительности на операциях с не выровненными данными?
Здравствуйте, sysenter, Вы писали:
RB>>У тебя неверное представление об архитектуре интеловских процессоров. RB>>Все, на самом деле, не так.
S>Если DMA изменит значение volatile переменной, а она у одного ядра закешированна в не разделяемом кеше L1, как это разруливается?
На x86 — путем "переговоров" между процессором и контроллером шины, полностью автоматизированных. На более других процессорах может и програмно разруливаться.
Здравствуйте, rus blood, Вы писали:
RB>Ты пишешь про хранение значений volatile-переменных в кеше процессора. RB>Я говорю про генерацию кода компилятором. RB>Где противоречие?
Противоречие, есть устройство (например датчик температуры реактора АЭС) с которого переменная volatile позволяет читать данные атомарно по одному byte или char за раз и каждый раз при чтении получаем новые данные.
Кешировать тут в кеше проца не просто бессмыслено, но и опасно. Если верить тебе то проц будет хранить значение этой переменной в кеше. Или не будет?
Здравствуйте, rus blood, Вы писали:
RB>2. То, что interlocked-операция работает как барьер — это фича реализации процессора ("расслабленность"). RB>Вполне могут быть архитектуры, где это не так, и требуются "настоящие" барьеры.
Это фича не процессора, а того API, в котором определены эти interlocked операции. Hint: вендовые InterlockedXXX() будут работать как барьер на любой архитектуре, где работает венда и понятие барьера имеет хоть какой-то физический смысл. А вот во что они превратятся "в ассемблерном виде", зависит от архитектуры.
RB>3. Интеловские процессоры достаточно "строгие" в смысле переупорядочивания. RB>В частности, твой пример flag=true; i=5; будет работать корректно — все другие ядра "увидят" изменения в правильном порядке.
Нет. И кроме того, что об этом прямо написано в интеловском мануале, я имел счастье в этом на опыте убедиться (отлаживать это место было весело).
Здравствуйте, Pzz, Вы писали:
Pzz>Это фича не процессора, а того API, в котором определены эти interlocked операции. Hint: вендовые InterlockedXXX() будут работать как барьер на любой архитектуре, где работает венда и понятие барьера имеет хоть какой-то физический смысл. А вот во что они превратятся "в ассемблерном виде", зависит от архитектуры.
Ок.
RB>>3. Интеловские процессоры достаточно "строгие" в смысле переупорядочивания. RB>>В частности, твой пример flag=true; i=5; будет работать корректно — все другие ядра "увидят" изменения в правильном порядке.
Pzz>Нет. И кроме того, что об этом прямо написано в интеловском мануале, я имел счастье в этом на опыте убедиться (отлаживать это место было весело).
Здравствуйте, Pzz, Вы писали:
Pzz>Че такое "принцип сосредоточенности во времени"?
Если была работа в некоторый момент времени с данными (чтение, запись) то велика вероятность, что в самом ближайшем будущем будет ещё работа с данными в том же месте или рядом.
Pzz>Аппаратура работает блоками размера N.
Вот из-за принципа сосредоточенности во времени и работает блоками т.к. дешевле прочитать сразу блок размером N чем по N раз читать по байту.
Pzz>Если данные не выровненны на границу блока, то да, одним обращением не обойдешься. N разный в зависимости от модели процессора и от места в нем (т.е., для работы с внешней памятью и для пересылок по внутренней шине N разный). Но я не уверен, что в современном процессоре есть хоть одно место, где N равен 4
Мне нужно прочитать 4-х байтовое значение, контролер доступа к памяти прочитал блок размером N (если не ошибаюсь 4096) и что дальше?
Он его кладёт в кеш и потом проц из кеша извлекает 4-х байтовое значение или проц сразу получает 4-х байтовое значение, а блок данных только после этого кладётся в кеш? Логично предположить, что второй вариант более вероятен, отсюда если данные выровнены то проц тратит меньше тактов чтобы поместить значение в регистр.
Pzz>А она есть на современных процессорах?
Здравствуйте, Pzz, Вы писали:
Pzz>DMA не проходит мимо чипсета. Чипсет умеет общаться с процессором. Pzz>Если устройство отображается на память, есть 2 варианта: либо пометить соответствующие страницы, как некешируемые, либо програмно сбрасывать кеш до/после изменения данных, в зависимости от направления передачи.
Здравствуйте, Pzz, Вы писали:
Pzz>Нет. И кроме того, что об этом прямо написано в интеловском мануале, я имел счастье в этом на опыте убедиться (отлаживать это место было весело).
Нет, ты это серъезно?
Вот мануал
"Intel 64 and IA-32 Architectures Software Developers Manual.3A.System Programming Guide.Part1.pdf"
Пункт 8.2.3.2
Example 8-1. Stores Are Not Reordered with Other Stores
---------------------------------
Processor 0 | Processor 1
---------------------------------
mov [ _x], 1 | mov r1, [ _y]
mov [ _y], 1 | mov r2, [ _x]
---------------------------------
Initially x == y == 0
r1 == 1 and r2 == 0 is not allowed
---------------------------------
Здравствуйте, rus blood, Вы писали:
RB>>>В частности, твой пример flag=true; i=5; будет работать корректно — все другие ядра "увидят" изменения в правильном порядке.
Pzz>>Нет. И кроме того, что об этом прямо написано в интеловском мануале, я имел счастье в этом на опыте убедиться (отлаживать это место было весело).
RB>А покажи, где это написано?
обсуждался такой случай. Судя по всему, не связынные по данным или управлению операции могут оказаться на разных конвейерах, и это в некоторых случаях может изменить порядок выполнения операций.
Здравствуйте, rus blood, Вы писали:
Pzz>>Нет. И кроме того, что об этом прямо написано в интеловском мануале, я имел счастье в этом на опыте убедиться (отлаживать это место было весело).
RB>А покажи, где это написано?
Берешь с интеловского сайыа мануал по любому современному интеловскому процессору, и ищешь. Мне понадобилось где-то с полчаса, чтобы найти. Закладку я, естественно, не поставил
обсуждался такой случай. Судя по всему, не связынные по данным или управлению операции могут оказаться на разных конвейерах, и это в некоторых случаях может изменить порядок выполнения операций.
Это вовсе не тот случай.
Это случай последовательных store-load по разным адресам, который как раз и может быть re-ordered процессором.
И не надо было столько копий ломать, когда это явно сказано в мануале:
8.2.3.4 Loads May Be Reordered with Earlier Stores to Different Locations
Здравствуйте, Pzz, Вы писали:
Pzz>Берешь с интеловского сайыа мануал по любому современному интеловскому процессору, и ищешь. Мне понадобилось где-то с полчаса, чтобы найти. Закладку я, естественно, не поставил
Мне не надо его брать с сайта.
Мануал есть у меня под рукой, и я перечитываю его постоянно 50 раз в секунду.
Процессор НЕ переупорядочивает последовательные store-store операции.
Здравствуйте, sysenter, Вы писали:
Pzz>>Аппаратура работает блоками размера N.
S>Вот из-за принципа сосредоточенности во времени и работает блоками т.к. дешевле прочитать сразу блок размером N чем по N раз читать по байту.
Работает блоками потому, что тупо мегагерцы наращивать уже больше не получается. Остается наращивать параллелизм.
S>Мне нужно прочитать 4-х байтовое значение, контролер доступа к памяти прочитал блок размером N (если не ошибаюсь 4096) и что дальше? S>Он его кладёт в кеш и потом проц из кеша извлекает 4-х байтовое значение или проц сразу получает 4-х байтовое значение, а блок данных только после этого кладётся в кеш? Логично предположить, что второй вариант более вероятен, отсюда если данные выровнены то проц тратит меньше тактов чтобы поместить значение в регистр.
Я в таких деталях не знаю. Но все значительно сложнее. Процессору надо для какой-то операции 4 байта данных. В кеше он поискал и не нашел. Он ставит в очередь запрос на обращение к памяти, и насколько может, едет дальше. Когда-нибудь там запрос к памяти чем-нибудь закончится, тогда процессор вернется к отложенной операции (по мере готовности ALU, а они может оказаться и занятым в момент, когда данные пришли), и что-нибудь с ней сделает. Попадают ли данные в регистр через промежуточную запись в кеш, или сразу туда и туда попадают, мне неизвестно. Но шина внутри самого процессора тоже, насколько я понимаю, не 4-байтная. Поэтому если эти 4 байта попадают в одну операцию шины, совсем не факт, что для процессора важно, как они выровненны.
S>А разве нет или я что-то пропустил?
Я слышал краем уха, что уже нет. За достоверность не поручусь.
Здравствуйте, rus blood, Вы писали:
RB>Это вовсе не тот случай. RB>Это случай последовательных store-load по разным адресам, который как раз и может быть re-ordered процессором. RB>И не надо было столько копий ломать, когда это явно сказано в мануале: RB>8.2.3.4 Loads May Be Reordered with Earlier Stores to Different Locations
Здравствуйте, rus blood, Вы писали:
Pzz>>Нет. И кроме того, что об этом прямо написано в интеловском мануале, я имел счастье в этом на опыте убедиться (отлаживать это место было весело).
RB>Нет, ты это серъезно?
Поверь на слово, а? Или давай поспорим на $100. Ну скучное это занятие, мануал читать, неохота мне сейчас забесплатно этим заниматься
Там написано, что каждое отдельное ядро само с собой self-consistent. Т.е., глядя с одного ядра, reordering заметить нельзя (это не значит, что его нет, но если следующая операция зависит от предыдущей, процессор сам ее притормозит, как надо). А вот между ядрами такого удобства нет.
И да, речь идет о более-менее современных процессорах. Что там было во времена 80486, я особо не интересовался
Здравствуйте, Pzz, Вы писали:
Pzz>Работает блоками потому, что тупо мегагерцы наращивать уже больше не получается. Остается наращивать параллелизм.
Оно всегда блоками происходило, чтение/запись, сколько себя помню.
Потому, что на время чтения/записи происходит блокирование линии и если гипотетически предположить, что можно было бы читать по байту, а не по блоку то большая часть времени тратилась бы на блокирование/разблокирование.
Pzz>Поэтому если эти 4 байта попадают в одну операцию шины, совсем не факт, что для процессора важно, как они выровненны. Pzz>Я слышал краем уха, что уже нет. За достоверность не поручусь.
Надеюсь просвещённая общественность подскажет, как оно на самом деле.
Здравствуйте, Pzz, Вы писали:
Pzz>Поверь на слово, а? Или давай поспорим на $100. Ну скучное это занятие, мануал читать, неохота мне сейчас забесплатно этим заниматься
Pzz>Там написано, что каждое отдельное ядро само с собой self-consistent. Т.е., глядя с одного ядра, reordering заметить нельзя (это не значит, что его нет, но если следующая операция зависит от предыдущей, процессор сам ее притормозит, как надо). А вот между ядрами такого удобства нет.
Pzz>И да, речь идет о более-менее современных процессорах. Что там было во времена 80486, я особо не интересовался
Не поверю, да и вера здесь не причем.
Мне эта тема важна для моей работы.
Что касается 486, то насколько я понял, со временем (с P6, кажется), процессор стал только более строгий.
Здравствуйте, rus blood, Вы писали:
RB>Мне не надо его брать с сайта. RB>Мануал есть у меня под рукой, и я перечитываю его постоянно 50 раз в секунду. RB>Процессор НЕ переупорядочивает последовательные store-store операции.
Вот представь себе, есть 3 переменные: a, b и c. a и c — соседи, живут в одном кеш-лайне и по вечерам ходят друг к другу на чай. А b живет в другом кеш-лайне и с ними не дружит.
Берем и пишем: a = 1; b = 2; c = 3; Что, по-твоему, процессор тот кеш-лайн, где живут a и c, два раза в память скинет?
Здравствуйте, Pzz, Вы писали:
Pzz>Вот представь себе, есть 3 переменные: a, b и c. a и c — соседи, живут в одном кеш-лайне и по вечерам ходят друг к другу на чай. А b живет в другом кеш-лайне и с ними не дружит. Pzz>Берем и пишем: a = 1; b = 2; c = 3; Что, по-твоему, процессор тот кеш-лайн, где живут a и c, два раза в память скинет?
А что, кем-то запрещено скидывать кеш-линии несколько раз в память?
И причем тут скидывание кеш-линий в память, и видимость изменений на другом ядре?
В данном примере.
После a=1 у первого ядра будет эксклюзивный доступ к первой кеш-линии, у второго ядра эта кеш-линия будет отмечена как dirty.
После b=2 то же самое будет со второй кеш-линией.
Теперь другое ядро читает b, т.е. происходит синхронизация второй кеш-линии.
Далее второе ядро читает переменную a, и выполняется синхронизация первой кеш-линии.
Если второе ядро видит в b значение 2, значит вторая кеш-линия уже изменена первым ядром.
Процессор гарантирует, что второе ядро увидит измененную версию первой кеш-линии (т.е. увидит, что a==1, и никак иначе).
Здравствуйте, rus blood, Вы писали:
RB>В данном примере. RB>После a=1 у первого ядра будет эксклюзивный доступ к первой кеш-линии, у второго ядра эта кеш-линия будет отмечена как dirty. RB>После b=2 то же самое будет со второй кеш-линией.
В мануалах интела с такой детализацией описывают синхронизацию кеша? Где-то я читал, что гарантируется только синхронизация кеша, а механизм скрыт.
Здравствуйте, 23W, Вы писали:
23W>Я понимаю что volatile отключает оптимизацию, т.е. гарантируется постоянная проверка переменной. Но ведь операция с байтом должна быть примитивной (атомарной).
Пример 1
Архитектура не умеет адресоваться к байтам. Только к словам. К байтам аддресация сделана софтверно, например через здвиги там всякие и т. п.
Пример 2
Аппаратура устроена так, что при попытке одновременной записи по одноу адресу из двух процессоров случается авост.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, rus blood, Вы писали:
RB>А что, кем-то запрещено скидывать кеш-линии несколько раз в память? RB>И причем тут скидывание кеш-линий в память, и видимость изменений на другом ядре?
ОК. Значит, я нарвался на то, что чтения и записи могут быть reordered (пришлось посмотреть мануал, ordering чтений/записей процессор гарантирует только если в одно и то же место обращаться).
Ужасно лень подымать change log большого проекта, искать там соответствующий патч и вспоминать, что именно от патчит
Здравствуйте, 23W, Вы писали:
23W>Уже несколько раз читал в разных местах, что volatile директива перед переменной не гарантирует атомарность операции с самой переменной, при работе с ней в несколько потоков. Так ли это ? 23W>Действительно, разве изменение volatile bool или volatile BYTE переменной может быть не атомарной ? ведь это примитивный байтовый тип изменяемый за одну asm операцию. Или все же нужно менять содержимое таких переменных через Intelock???
Иногда правильный ответ гораздо проще получить на практике, чем читать весь тот срач что устроили "специалисты" (а правильно вам подсказывают только в последних ветках)
#define MAX_THREAD 4
#define MAX_INC 10000000
/* bool == BYTE == char */long a = 0; /*char a = 0; */volatile long b = 0; /*volatile char b = 0;*/long c = 0;
DWORD WINAPI start(LPVOID lpThreadParameter)
{
int i = 0;
for (i = 0; i < MAX_INC; i++)
{
a++;
b++;
InterlockedIncrement(&c);
}
}
int main(int argc, char *argv[])
{
HANDLE h[MAX_THREAD];
DWORD n = 0;
int i = 0;
for (i = 0; i < MAX_THREAD; i++) {
h[i] = CreateThread(NULL, 0, start, NULL, 0, &n);
if ( INVALID_HANDLE_VALUE == h[i]) ExitProcess(-1);
}
WaitForMultipleObjects(MAX_THREAD, &h, TRUE, INFINITE);
printf("%i \t a = %i \t b = %i \t c = %i", MAX_THREAD*MAX_INC, a, b, c);
/*printf("%i \t a = %i \t b = %i \t c = %i", 0xff & (MAX_THREAD*MAX_INC), a, b, (0xff & c) ); */
getchar();
return 0;
}
ЗЫ. Обратный тест (если с volatile long было верное значение — некоректен) — по неправильной работе в частном случае можно утверждать неправильность работы в целом. Но по правильной работе в частном случае невозможно утверждать о правильной работе в целом.