Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, remark, Вы писали:
R>>Идея такая, что если какой-то глобальный объект использует наш синглтон, то data.h должен быть подключен в этот файл, и data_initializer_ будет определен в этом файле раньше того глобального объекта.
AG>Раньше определён — раньше создан ? Почему ?
Это всегда гарантировалось — если глобальный объект определен раньше другово в единице трансляции, то он и создастся раньше. Это только между разными единицами трансляции порядок не определен.
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, remark, Вы писали:
R>>"Для порядка". Должен был быть и барьер компилятора и аппаратный. Аппаратный убрали, компилятора должен остаться. Хотя, возможно, из-за зависимости по-управлению, он тут и не обязателен, но это такая область, где лучше переборщить, чем недоборщить.
AG>Может у меня не хватает фантазии, но я не редставляю, как компилятор может испортить. Мне кажется, что такое
AG>при этом барьер на патологический случай, если копилятор вздумает делать что-то до проверки указателя, а потом, при успешной проверке, отменять что наделал.
А смысл, если _ReadWriteBarrier() ничего не стОит в ран-тайм?
AG>>>Ещё, можно ли убрать volatile и заменить его на _WriteBarrier(); перед сохранением указателя ?
R>>Нет. R>>Ну точнее так — на x86 должно работать. А на IA-64 обязателен аппаратный барьер.
AG>http://msdn.microsoft.com/en-us/library/f20w0x5e(VS.80).aspx AG>
AG>Marking memory with a memory barrier is similar to marking memory with the volatile (C++) keyword.
AG>?
У них, честно говоря, такая дерьмовая документация, что уже отчаялся в ней что-либо понять. Нет, что б явно и чётко всё написать, всё какими-то намёками выражаются...
По моим представлениям, если бы эти _ReadWriteBarrier/_WriteBarrier/_ReadBarrier были бы и аппаратными барьерами, то на x86 _ReadWriteBarrier должен был бы выливаться в инструкцию mfence. Этого не происходит. Поэтому я считаю, что это — только барьеры компилятора... Хотя интересно было бы поглядеть как _WriteBarrier компилируется на IA-64 — я не знаю...
Здравствуйте, remark, Вы писали:
R>У них, честно говоря, такая дерьмовая документация, что уже отчаялся в ней что-либо понять. Нет, что б явно и чётко всё написать, всё какими-то намёками выражаются... R>По моим представлениям, если бы эти _ReadWriteBarrier/_WriteBarrier/_ReadBarrier были бы и аппаратными барьерами, то на x86 _ReadWriteBarrier должен был бы выливаться в инструкцию mfence. Этого не происходит. Поэтому я считаю, что это — только барьеры компилятора... Хотя интересно было бы поглядеть как _WriteBarrier компилируется на IA-64 — я не знаю...
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, remark, Вы писали: А>...
А>А какой практический смысл есть в чисто "НЕ аппаратных" барьерах, если, полюбому, на процессоре всё может перемешаться, как ему угодно?
Есть.
С их помощью можно (1) упорядочивать между потоком и обработчиком сигнала для этого же потока, (2) упорядочивать между 2 потоками, которые выполняются на одном процессоре, (3) использовать, если упорядочивание на аппаратном уровне выполняется другими средствами (обычно в таком случае барьер компилятора стоит при всех обращениях, т.к. он бесплатный; а цена аппаратного барьера амортизируется на множество обращений; хороший пример RCU в ядре Linux).
Так же стоит отметить, что барьеры компилятора включены в C++0x — там смотри std::atomic_signal_fence(std::memory_order).
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, remark, Вы писали:
R>>У них, честно говоря, такая дерьмовая документация, что уже отчаялся в ней что-либо понять. Нет, что б явно и чётко всё написать, всё какими-то намёками выражаются... R>>По моим представлениям, если бы эти _ReadWriteBarrier/_WriteBarrier/_ReadBarrier были бы и аппаратными барьерами, то на x86 _ReadWriteBarrier должен был бы выливаться в инструкцию mfence. Этого не происходит. Поэтому я считаю, что это — только барьеры компилятора... Хотя интересно было бы поглядеть как _WriteBarrier компилируется на IA-64 — я не знаю...
AG>x86 AG>x64 AG>никакой mfence на х86 и х64 не нужно ?
На IA-32 и Intel 64 (x86-32/64) барьеры не нужны. А на IA-64 (Itanium) при volatile обращениях ты должен увидеть инструкции ld.acq/st.rel (load-acquire/store-release) вместо обычных ld/st (load/store).
Здравствуйте, remark, Вы писали:
R>На IA-32 и Intel 64 (x86-32/64) барьеры не нужны. А на IA-64 (Itanium) при volatile обращениях ты должен увидеть инструкции ld.acq/st.rel (load-acquire/store-release) вместо обычных ld/st (load/store).
Ну т.е. нет никаких свидетельств что барьеры "хуже" volatile ?
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, remark, Вы писали:
R>>На IA-32 и Intel 64 (x86-32/64) барьеры не нужны. А на IA-64 (Itanium) при volatile обращениях ты должен увидеть инструкции ld.acq/st.rel (load-acquire/store-release) вместо обычных ld/st (load/store).
AG>Ну т.е. нет никаких свидетельств что барьеры "хуже" volatile ?
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, remark, Вы писали:
R>>В смысле хуже?
AG>Я имею ввиду, что можно заменить voiatile на барьеры, как пишут в MSDN, т.к. машииный код такой же
Мммм... ну у меня всё ещё остаётся подозрение, что volatile в MSVC — это и барьер компилятора и аппаратный, в барьеры — это только компилятора. Т.к. иначе не понятно почему _ReadWriteBarrier() не эмитит mfence на x86.
Плюс они, так сказать, ортогональны друг другу. В том смысле, что например при захвате мьютекса нужен барьер типа load-acquire (или #LoadLoad|#LoadStore membar в терминологии SPARC), и volatile load даёт именно это; точно же смоделировать это с помощью барьеров не получается, т.к. _ReadBarrier() и _WriteBarrier() каждый по отдельности недостаточны, а _ReadWriteBarrier() слишком тяжелый (хммм... точнее он бы слишком тяжелым, если бы эмитил mfence, а так вообще не понятно, что это такое). Честно говоря, я чем больше читаю их документацию, тем больше запутываюсь
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, remark, Вы писали:
R>>Т.к. иначе не понятно почему _ReadWriteBarrier() не эмитит mfence на x86.
AG>Зачем там mfence ?
Полный барьер на x86 должен отображаться на mfence.
AG>Кстати, почему-то __mf intrinsic только для Itanium. Видимо, кроме инлайн ассемблера никак этот самый _mfence на х86 не сделать. нужен ли он ?
_mm_mfence() в заголовке <intrin.h>. Да, нужен для многих алгоритмов синхронизации.
Здравствуйте, Alexander G, Вы писали:
AG>Ну т.е. нет никаких свидетельств что барьеры "хуже" volatile ?
AG>(ассемблера IA64 не знаю, для IA64 не собираю)
R>>
Полностью согласен с remark, однако хотелось бы прояснить некоторые моменты:
Для процессоров IA-32 и Intel 64 (Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Intel Xeon, P6 family, Pentium 4 и более ранних),
AMD64 и более ранних операции чтения из памяти и запись всегда имеют семантику Acquire и Release соответственно (если не считать "string operations" и "streaming stores"). Однако это не мешает и вышеописанные процессоры (кроме совсем ранних) переупорядочивают чтение и предшествующую запись, если они не относятся к одному адресу памяти.
volatile long x = 0;
volatile long y = 0;
....
//Processor A
x = 1;
long r1 = y;
....
//Processor B
y = 1;
long r2 = x;
Из-за возможного переупорядочивания в результате выполнения возможна ситуация, когда r1 = r2 = 0
Некоторые алгоритмы могут полагаться на строгую последовательность операций чтения и предшествующей записи, поэтому реализация барьера для процессора должна содержать либо инструкцию mfence, либо инструкцию с префиксом "LOCK" в случае вышеописанных процессоров. Соответсвенно, по факту функция _ReadWriteBarrier не реализует барьер памяти для процессора, а лишь барьер памяти для компилятора:
A memory barrier prevents the compiler from optimizing memory accesses across the barrier, but enables the compiler to still optimize instructions between barriers.
Единственное, что вводит в ложное заблуждение в документации Microsoft, так это:
Marking memory with a memory barrier is similar to marking memory with the volatile (C++) keyword.
Здравствуйте, Quasi, Вы писали:
Q>Для процессоров IA-32 и Intel 64 (Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Intel Xeon, P6 family, Pentium 4 и более ранних), Q>AMD64 и более ранних операции чтения из памяти и запись всегда имеют семантику Acquire и Release соответственно (если не считать "string operations" и "streaming stores"). Однако это не мешает и вышеописанные процессоры (кроме совсем ранних) переупорядочивают чтение и предшествующую запись, если они не относятся к одному адресу памяти.
АФАИК, они переупорядочивают даже обращения к одному адресу памяти. Свои обращения процессор-то, конечно, увидит в программном порядке, а вот другие процессоры могут увидеть обращения к одному адресу памяти в отличном от программного.
Единственное, что сейчас видимо не переупорядочивается (хотя АФАИК это не документировано) — это запись и чтение по одному адресу, но с разными размерами (т.н. collocation trick):
volatile char x = 0;
volatile char y = 0;
// thread 1
x = 1;
short r1 = *(volatile short*)&x;
// thread 2
y = 1;
short r2 = *(volatile short*)&x;
Q>Из-за возможного переупорядочивания в результате выполнения возможна ситуация, когда r1 = r2 = 0 Q>Некоторые алгоритмы могут полагаться на строгую последовательность операций чтения и предшествующей записи, поэтому реализация барьера для процессора должна содержать либо инструкцию mfence, либо инструкцию с префиксом "LOCK" в случае вышеописанных процессоров. Соответсвенно, по факту функция _ReadWriteBarrier не реализует барьер памяти для процессора, а лишь барьер памяти для компилятора: Q>
Q>A memory barrier prevents the compiler from optimizing memory accesses across the barrier, but enables the compiler to still optimize instructions between barriers.
Q>Единственное, что вводит в ложное заблуждение в документации Microsoft, так это: Q>
Marking memory with a memory barrier is similar to marking memory with the volatile (C++) keyword.
А почему это вводит в заблуждение? Ведь volatile в данном случае тоже не приведёт к добавлению mfence/lock.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, Quasi, Вы писали:
Q>>Для процессоров IA-32 и Intel 64 (Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Intel Xeon, P6 family, Pentium 4 и более ранних), Q>>AMD64 и более ранних операции чтения из памяти и запись всегда имеют семантику Acquire и Release соответственно (если не считать "string operations" и "streaming stores"). Однако это не мешает и вышеописанные процессоры (кроме совсем ранних) переупорядочивают чтение и предшествующую запись, если они не относятся к одному адресу памяти.
R>АФАИК, они переупорядочивают даже обращения к одному адресу памяти. Свои обращения процессор-то, конечно, увидит в программном порядке, а вот другие процессоры могут увидеть обращения к одному адресу памяти в отличном от программного.
В общем, с этой точки зрения да, такое поведение документированно.
R>А почему это вводит в заблуждение? Ведь volatile в данном случае тоже не приведёт к добавлению mfence/lock.
MSDN, volatile:
This allows volatile objects to be used for memory locks and releases in multithreaded applications
В то время как, _ReadWriteBarrier (
is similar to marking memory with the volatile (C++) keyword
) никакого отношения к упорядочиванию операций в памяти процессором не имеет, по факту.
Принципиальным здесь является наличие зависимости по данным, которая гарантирует, что на всех современных процессорах чтения указателя p_data и данных по указателю не будут переупорядочены. Однако, это не налагает ограничений на переупорядочивание записи p_data и данных. В данном случае для предотвращения переупорядочивания достаточно записи указателя p_data c release семантикой (на x86, Intel 64 и AMD64 для этого ничего дополнительно не требуется). Если верить документации Microsoft по квалификатору volatile, то для переносимости на другие аппаратные платформы можно воспользоваться трюком, который описал remark:
На других компиляторах без соответствующих acquire, release семантик для чтения, записи volatile данных можно использовать атомарную операцию, аналогичную ::InterlockedExchange в WINAPI, имеющую либо release, либо fence семантику (full memory barrier, как в случае с ::InterlockedExchange).
Q> В данном случае для предотвращения переупорядочивания достаточно записи указателя p_data c release семантикой (на x86, Intel 64 и AMD64 для этого ничего дополнительно не требуется).
Я не зочу volatile т.к. его смысл зависит от компилятора.
Может тогда так ?
data_t* init()
{
if (p_data)
return p_data;
_ReadWriteBarrier();
data.p1 = ::GetModuleHandle(...);
data.p2 = ::GetProcAddress(...);
data.i3 = (int)sqrt(42*_WIN_VER);
#if defined(_M_IX86) || defined(_M_IA64) || defined(_M_X64)
// soft write barrier is enough
_WriteBarrier();
p_data = &data;
#else
#error add a proper barrier for this architecture
#endif
return p_data;
}