Здравствуйте, emusic, Вы писали:
E>А вот при наличии volatile при каждом обращении к такому объекту формируется sequence point.
Откуда это следует? В стандарте написано, что обращение к volatile-объектам встроенных типов является частью обозримого поведения (observable behaviour) программы, а следовательно не может быть устранено средствами оптимизации. Но это не имеет никакого отношения к точкам следования.
Я кончил, джентльмены, мне остается только поблагодарить вас за внимание.
Здравствуйте, emusic, Вы писали:
E>Надо отдать должное реализации этих функций: если секция не захвачена другим потоком — объекты синхронизации не задействуются, все выливается только в вызовы InterlockedXXX.
Собственно, в этом их raison d'etre. Иначе мьютексов бы хватало.
Я кончил, джентльмены, мне остается только поблагодарить вас за внимание.
[]
E>Если тебе удается успешно обходиться без volatile, одними примитивами — это не более, чем случайность. Весьма, впрочем, распространенная ввиду того, что нынешние оптимизаторы в большинстве случаев справедливо опасаются использовать кэшированные ранее значения после вызова неизвестной функции (тот самый aliasing, разговор о котором ты предпочел не развивать). Но, попадись такой код мощному оптимизатору, который не поленится проанализировать вызываемую функцию, или окажись примитивы синхронизации реализованными в более явном виде, этот код нормально работать перестанет. И неожиданным это будет лишь для тебя
Я скомпилировал следующий код на VC71 и Intel 8 с максимальными настройками оптимизации, с LTCG/IPO и без. Во всех четырех случаях при чтении переменная не кэшировалась, неважно, volatile ли была переменная или нет.
Оптимизатор, более мощный чем у VC71, мне не известен. Пример прост, и, скорее всего, более легкий пример для оптимизатора сложно придумать.
Хотелось бы увидеть multithreaded код, где volatile реально чем-то помогает.
// main.cpp#include <stdio.h>
#include"windows.h"extern void foo();
long a;
long b;
long volatile c;
void f()
{
while(!a)
Sleep(20);
printf("%s is done\n", __FUNCTION__);
}
void g()
{
while(!b)
Sleep(20);
printf("%s is done\n", __FUNCTION__);
}
void h()
{
while(!c)
Sleep(20);
printf("%s is done\n", __FUNCTION__);
}
int main()
{
foo();
f();
g();
h();
}
Здравствуйте, MaximE, Вы писали:
ME>Я скомпилировал следующий код на VC71 и Intel 8 с максимальными настройками оптимизации, с LTCG/IPO и без. Во всех четырех случаях при чтении переменная не кэшировалась, неважно, volatile ли была переменная или нет.
Возможно, дело в вызовах Sleep; компилятор не имеет возможности проверить, не изменяет ли Sleep переменные, поэтому генерирует код из предположения, что переменная там может измениться.
Я кончил, джентльмены, мне остается только поблагодарить вас за внимание.
Здравствуйте, MaximE, Вы писали:
ME>Здравствуйте, emusic, Вы писали:
ME>[]
E>>Если тебе удается успешно обходиться без volatile, одними примитивами — это не более, чем случайность. Весьма, впрочем, распространенная ввиду того, что нынешние оптимизаторы в большинстве случаев справедливо опасаются использовать кэшированные ранее значения после вызова неизвестной функции (тот самый aliasing, разговор о котором ты предпочел не развивать). Но, попадись такой код мощному оптимизатору, который не поленится проанализировать вызываемую функцию, или окажись примитивы синхронизации реализованными в более явном виде, этот код нормально работать перестанет. И неожиданным это будет лишь для тебя
ME>Я скомпилировал следующий код на VC71 и Intel 8 с максимальными настройками оптимизации, с LTCG/IPO и без. Во всех четырех случаях при чтении переменная не кэшировалась, неважно, volatile ли была переменная или нет.
Ты не совсем прав в данном случае. Нет никаких гарантий, что подобное поведение сохраниться в будующем. В этом примере компилятор не смог оценить побочные эффекты от вызова Sleep, поэтому и перечитал переменную из памяти. А вот теперь уберем Sleep. И получим звездец. И в будующем ситуация может стать аналогичной и с вызовом Sleep. Дело в том, что существует предложение о введении спецификатора для функций без побочных эффектов (pure). Если это будет реализовано и функция Sleep будет помечена как pure, то и её вызов не будет рассматриваться компилятором как могущий изменить переменную a, что приведёт к нежелательной оптимизации.
volatile int sleep_var;
void sleep(int n) // Побочные эффекты от этой функции компилятор может оценить. См. ниже, к чему это приводит.
{
sleep_var=n;
for(; sleep_var ;sleep_var--);
}
long a;
volatile long b;
void f()
{
while(!a)
sleep(20);
}
void g()
{
while(!b)
sleep(20);
}
_TEXT SEGMENT
?f@@YAXXZ PROC NEAR ; f, COMDAT
; 21 : while(!a)
mov eax, DWORD PTR ?a@@3JA ; a
test eax, eax
jne SHORT $L285
mov eax, 20 ; 00000014H
npad 2
$L284:
; 22 : sleep(20);
mov DWORD PTR ?sleep_var@@3HC, eax ; sleep_var
mov ecx, DWORD PTR ?sleep_var@@3HC ; sleep_var
test ecx, ecx
je SHORT $L284
npad 1
$L312:
mov ecx, DWORD PTR ?sleep_var@@3HC ; sleep_var
dec ecx
mov DWORD PTR ?sleep_var@@3HC, ecx ; sleep_var
jne SHORT $L312
; 21 : while(!a)
jmp SHORT $L284
$L285:
; 23 : }
ret 0
?f@@YAXXZ ENDP ; f
_TEXT ENDS
PUBLIC ?g@@YAXXZ ; g
; Function compile flags: /Ogtpy
; COMDAT ?g@@YAXXZ
_TEXT SEGMENT
?g@@YAXXZ PROC NEAR ; g, COMDAT
; 27 : while(!b)
mov eax, DWORD PTR ?b@@3JC ; b
test eax, eax
jne SHORT $L290
mov eax, 20 ; 00000014H
npad 2
$L289:
; 28 : sleep(20);
mov DWORD PTR ?sleep_var@@3HC, eax ; sleep_var
mov ecx, DWORD PTR ?sleep_var@@3HC ; sleep_var
test ecx, ecx
je SHORT $L326
npad 1
$L324:
mov ecx, DWORD PTR ?sleep_var@@3HC ; sleep_var
dec ecx
mov DWORD PTR ?sleep_var@@3HC, ecx ; sleep_var
jne SHORT $L324
$L326:
mov ecx, DWORD PTR ?b@@3JC ; b
test ecx, ecx
je SHORT $L289
$L290:
; 29 : }
ret 0
?g@@YAXXZ ENDP ; g
_TEXT ENDS
Шахтер wrote:
> ME>Я скомпилировал следующий код на VC71 и Intel 8 с максимальными настройками оптимизации, с LTCG/IPO и без. Во всех четырех случаях при чтении переменная не кэшировалась, неважно, volatile ли была переменная или нет. > > Ты не совсем прав в данном случае. Нет никаких гарантий, что подобное поведение сохраниться в будующем. В этом примере компилятор не смог оценить побочные эффекты от вызова Sleep, поэтому и перечитал переменную из памяти. А вот теперь уберем Sleep. И получим звездец.
Ты хочешь сказать, что добавим volatile — и все ok? Действительно, это может заставить компилятор прочитать значение из памяти, а может и нет, так как семантика volatile — implementation defined.
Чтение из памяти на текущих процессорах Intel гарантировано прочитает актуальное значение, даже если память была изменена в кэше другого процессора, т.к. Intel для текущих процессоров применяет cache snooping.
When operating in an MP system, IA-32 processors (beginning with the Intel486 processor) have the ability to snoop other processor’s accesses to system memory and to their internal caches. They use this snooping ability to keep their internal caches consistent both with system memory and with the caches in other processors on the bus. For example, in the Pentium and P6 family processors, if through snooping one processor detects that another processor intends to write to a memory location that it currently has cached in shared state, the snooping processor will invalidate its cache line forcing it to perform a cache line fill the next time it accesses the same memory location.
> И в будующем ситуация может стать аналогичной и с вызовом Sleep. Дело в том, что существует предложение о введении спецификатора для функций без побочных эффектов (pure). Если это будет реализовано и функция Sleep будет помечена как pure, то и её вызов не будет рассматриваться компилятором как могущий изменить переменную a, что приведёт к нежелательной оптимизации.
В будущем для Intel и в настоящем для многих других процессоров, volatile точно не поможет, так как на Intel он опирается на две processor dependent фичи: на memory ordering и когерентность кешей (cache snooping выше).
7.2.1. Memory Ordering in the Pentium® and Intel486™ Processors
The Pentium and Intel486 processors follow the processor-ordered memory model; however, they operate as strongly-ordered processors under most circumstances. Reads and writes always appear in programmed order at the system bus—except for the following situation where processor ordering is exhibited. Read misses are permitted to go ahead of buffered writes on the system bus when all the buffered writes are cache hits and, therefore, are not directed to the same address being accessed by the read miss.
In the case of I/O operations, both reads and writes always appear in programmed order.
Software intended to operate correctly in processor-ordered processors (such as the Pentium 4, Intel Xeon, and P6 family processors) should not depend on the relatively strong ordering of the Pentium or Intel486 processors. Instead, it should insure that accesses to shared variables that are intended to control concurrent execution among processors are explicitly required to obey program ordering through the use of appropriate locking or serializing operations (see Section 7.2.4., “Strengthening or Weakening the Memory Ordering Model”).
...
7.2.4. Strengthening or Weakening the Memory Ordering Model
...
It is recommended that software written to run on Pentium 4, Intel Xeon, and P6 family processors assume the processor-ordering model or a weaker memory-ordering model. The Pentium 4,
Intel Xeon, and P6 family processors do not implement a strong memory-ordering model, except when using the UC memory type. Despite the fact that Pentium 4, Intel Xeon, and P6 family processors support processor ordering, Intel does not guarantee that future processors will support this model. To make software portable to future processors, it is recommended that operating systems provide critical region and resource control constructs and API’s (application
program interfaces) based on I/O, locking, and/or serializing instructions be used to synchronize access to shared areas of memory in multiple-processor systems. Also, software should not depend on processor ordering in situations where the system hardware does not support this memory-ordering model.
Так что volatile не является ни необходимым, ни достаточным для multithreading. Если малтитредовой проге необходим volatile чтобы корректно работать, такая программа непортабельна.
Здравствуйте, MaximE, Вы писали:
ME>Шахтер wrote:
>> ME>Я скомпилировал следующий код на VC71 и Intel 8 с максимальными настройками оптимизации, с LTCG/IPO и без. Во всех четырех случаях при чтении переменная не кэшировалась, неважно, volatile ли была переменная или нет. >> >> Ты не совсем прав в данном случае. Нет никаких гарантий, что подобное поведение сохраниться в будующем. В этом примере компилятор не смог оценить побочные эффекты от вызова Sleep, поэтому и перечитал переменную из памяти. А вот теперь уберем Sleep. И получим звездец.
ME>Ты хочешь сказать, что добавим volatile — и все ok?
Да. Именно для этого это ключевое слово и предназнчается.
ME>Чтение из памяти на текущих процессорах Intel гарантировано прочитает актуальное значение, даже если память была изменена в кэше другого процессора, т.к. Intel для текущих процессоров применяет cache snooping. ...
А какое это отношение имеет к обсуждаемому вопросу? volatile -- это средство ограничения оптимизации кода. Пример того, как оно работает, был приведён выше. К тому, что происходит в процессоре, это не имеет никакого отношения.
Здравствуйте, MaximE, Вы писали:
ME>Я скомпилировал следующий код на VC71 и Intel 8 с максимальными настройками оптимизации, с LTCG/IPO и без.
В этих примерах используются глобальные переменные, и глобальные же функции. Откуда оптимизатору знать, что функция Sleep не изменяет этих переменных?
Если переделать все переменные и функции на статические, и добавить ключ запрета aliasing'а — оптимизатор вполне может начать кэшировать значения. Хотя в VC++ 7.0 оптимизатор это делать ленится.
ME>Хотелось бы увидеть multithreaded код, где volatile реально чем-то помогает.
Я уже приводил такой код. Приведу в более убедительном виде:
#include <windows.h>
CRITICAL_SECTION CS;
int f (int *a, int *b) {
EnterCriticalSection (&CS);
if (!*a) {
LeaveCriticalSection (&CS);
Sleep (100);
EnterCriticalSection (&CS);
*b = *a;
}
LeaveCriticalSection (&CS);
return *b;
}
Если скомпилить его VC++ 6/7 с ключом /Oa — в присваивании внутри условия используется константа 0. Разумеется, код притянут за уши, но он наглядно показывает, что оптимизатор может кэшировать значение объекта, если у того нет volatile. aliasing'а в данном примере нет — нигде в программе другого доступа к объектам *a и *b быть не обязано. Без параллелизма данный код будет работать всегда. С параллелизмом — сам догадайся
Большинство примеров кода без volatile работает в многопоточной среде только за счет того, что компилятор до последнего подозревает о наличии этого самого aliasing'а — что любая из вызываемых функций, код которой нельзя проанализировать сейчас же, потенциально может изменить переменную через указатель на нее. Именно поэтому после вызова функций переменные перечитываются, а отнюдь не из-за sequience points.
Здравствуйте, emusic, Вы писали:
E>Здравствуйте, MaximE, Вы писали:
ME>>Я скомпилировал следующий код на VC71 и Intel 8 с максимальными настройками оптимизации, с LTCG/IPO и без.
E>В этих примерах используются глобальные переменные, и глобальные же функции. Откуда оптимизатору знать, что функция Sleep не изменяет этих переменных?
E>Если переделать все переменные и функции на статические, и добавить ключ запрета aliasing'а — оптимизатор вполне может начать кэшировать значения. Хотя в VC++ 7.0 оптимизатор это делать ленится.
ME>>Хотелось бы увидеть multithreaded код, где volatile реально чем-то помогает.
E>Я уже приводил такой код. Приведу в более убедительном виде:
E>
E>Если скомпилить его VC++ 6/7 с ключом /Oa — в присваивании внутри условия используется константа 0. Разумеется, код притянут за уши, но он наглядно показывает, что оптимизатор может кэшировать значение объекта, если у того нет volatile. aliasing'а в данном примере нет — нигде в программе другого доступа к объектам *a и *b быть не обязано. Без параллелизма данный код будет работать всегда. С параллелизмом — сам догадайся
E>Большинство примеров кода без volatile работает в многопоточной среде только за счет того, что компилятор до последнего подозревает о наличии этого самого aliasing'а — что любая из вызываемых функций, код которой нельзя проанализировать сейчас же, потенциально может изменить переменную через указатель на нее. Именно поэтому после вызова функций переменные перечитываются, а отнюдь не из-за sequience points.
Погоди. Тут что-то не так. a или b вполне могут указывать внутрь критической секции. В этом случае оптимизация незаконна.
Шахтер wrote:
> ME>Чтение из памяти на текущих процессорах Intel гарантировано прочитает актуальное значение, даже если память была изменена в кэше другого процессора, т.к. Intel для текущих процессоров применяет cache snooping. ... > > А какое это отношение имеет к обсуждаемому вопросу? volatile -- это средство ограничения оптимизации кода. Пример того, как оно работает, был приведён выше. К тому, что происходит в
процессоре, это не имеет никакого отношения.
[]
> Если скомпилить его VC++ 6/7 с ключом /Oa — в присваивании внутри условия используется константа 0. Разумеется, код притянут за уши, но он наглядно показывает, что оптимизатор может кэшировать значение объекта, если у того нет volatile. aliasing'а в данном примере нет — нигде в программе другого доступа к объектам *a и *b быть не обязано. Без параллелизма данный код будет работать всегда. С параллелизмом — сам догадайся
Я про это и пишу:
Так что volatile не является ни необходимым, ни достаточным для multithreading. Если малтитредовой проге необходим volatile чтобы корректно работать, такая программа непортабельна.
> Большинство примеров кода без volatile работает в многопоточной среде только за счет того, что компилятор до последнего подозревает о наличии этого самого aliasing'а — что любая из вызываемых функций, код которой нельзя проанализировать сейчас же, потенциально может изменить переменную через указатель на нее. Именно поэтому после вызова функций переменные перечитываются, а отнюдь не из-за sequience points.
Да, с этим я согласен, про sequience points, похоже, я херню написал.
Здравствуйте, MaximE, Вы писали:
ME>Vet wrote:
>> В объекте класса есть переменная, которая используется двумя потоками. >> Один из потоков иногда меняет ее значение. >> Есть ли необходимость в этом случае делать переменную как volatile. >> И зависит ли ответ от того, сздан ли объект класса на стеке или в динамической памяти. >> >> Вопрос вызван тем, что у Рихтера прочитал, что если глобальная переменная используется >> разными потоками, то она обязана быть volatile. Вот меня и рабирают сомнения нужно ли >> тоже самое делать для переменных(членов) класса.
ME>Рихтер ошибался.
ME>volatile не имеет никакого отношения к multithreading, поэтому его применение в этой ситуации не только бесполезно, но может быть и вредно, так как с volatile компилятор не сможет соптимизировтать доступ к этой переменной. Но если один из потоков изменяет переменную, синхронизация при помощи мютексов обязательна.
ME>-- ME>Maxim Yegorushkin
Абсолютно неверный и опасный совет. Как раз volatile тут необходим, как в прочем и желательна защита обьекта при помощи примитивов синхронизации. volatile нужен дабы избежать лишней оптимизации (компилятор может моложить переменную в регистр, и защищая её или нет, но другой поток об том, что переменная изменилась — не узнает никогда, или несвоевременно)
Здравствуйте, Шахтер, Вы писали:
Ш>Погоди. Тут что-то не так. a или b вполне могут указывать внутрь критической секции. В этом случае оптимизация незаконна.
Мне сколько раз нужно повторить слово aliasing, чтобы оно было, наконец, воспринято? Указание ключа /Oa сообщает компилятору, что в программе подобные совмещения исключены.
Здравствуйте, MaximE, Вы писали:
ME>Я про это и пишу: ME>
ME>Так что volatile не является ни необходимым, ни достаточным для multithreading. Если малтитредовой проге необходим volatile чтобы корректно работать, такая программа непортабельна.
Обосновать это свое утверждение чем-либо, кроме собственной смутной интуиции, можешь? Я тебе привел пример программы, которая без volatile корректно работать не будет. Найди в этом примере ошибку, либо приведи доказательство того, что любая многопоточная программа обязана корректно работать без volatile. Пока бОльшую часть приведенных тебе аргументов ты тихо опускаешь, повторяя в ответ одни и те же измышления.
[]
> Абсолютно неверный и опасный совет. Как раз volatile тут необходим, как в прочем и желательна защита обьекта при помощи примитивов синхронизации. volatile нужен дабы избежать лишней оптимизации (компилятор может моложить переменную в регистр, и защищая её или нет, но другой поток об том, что переменная изменилась — не узнает никогда, или несвоевременно)
[]
> Обосновать это свое утверждение чем-либо, кроме собственной смутной интуиции, можешь? Я тебе привел пример программы, которая без volatile корректно работать не будет. Найди в этом примере ошибку, либо приведи доказательство того, что любая многопоточная программа обязана корректно работать без volatile. Пока бОльшую часть приведенных тебе аргументов ты тихо опускаешь, повторяя в ответ одни и те же измышления.
emusic wrote:
> Ш>Погоди. Тут что-то не так. a или b вполне могут указывать внутрь критической секции. В этом случае оптимизация незаконна. > > Мне сколько раз нужно повторить слово aliasing, чтобы оно было, наконец, воспринято? Указание ключа /Oa сообщает компилятору, что в программе подобные совмещения исключены.
... но на самом деле они у тебя в проге есть и ты полагаешься на результат этого альясинга. Ты сообщил компилятору неверную информацию — all bets are off.
Макс, это уже не смешно.
Или давай технические спеки или ссылки на докуметнацию компиляторов.
Короче что-нибудь, но не ссылки на threads в дискуссиях ведущихся анонимными авторами.
"Весь мир давно заездил эту тему" ты имеешь ввиду себя и пару тройку "просвященных"?
Примерно 25% этого мира посещяет RSDN на котором мы видим "луч света в темном царстве" в твоем лице.
Чего ты уперся? volatile is a C++ keyword. Period.