Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Serginio1, Вы писали:
VD>Я вот тоже слышал этот термин несклько раз, но он как-то упоминается в сколь. Типа "обсуждение этой фичи выходит за рамки...".
Во блин нашел аж за 1993 год http://research.sun.com/research/self/papers/write-barrier.html
Каким то образом наверное write barier связан с GC но это возможно если учет ссылок GC был произведен, но обычно это происходит при выделении определенного количества памяти. С другой стороны на Array.Copy write-barrier практически не влияет (вернее есть небольшое замедление по сравнению с валуе типами, но не такое катострофическое, причем в видби это практически не заметно).
... << RSDN@Home 1.1.0 stable >>
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Serginio1, Вы писали:
VD>Я вот тоже слышал этот термин несклько раз, но он как-то упоминается в сколь. Типа "обсуждение этой фичи выходит за рамки...".
Интересно что, при инлайне тормозов нет (веренее есть но только в 2 раза по ставнению с int)
public class IntClass
{
public int value;
public IntClass(int val)
{
value = val;
}
}
public static void Run8(System.Windows.Forms.TextBox _log, int Count)
{
_log.AppendText("Сортировка вставка IntClass массива Count=" + Count.ToString() + Environment.NewLine);
Utils.PerfCounter timer = new PerfCounter();
int r = 666;
IntClass[] IntClassArray = new IntClass[Count];
IntClass[] IntClassArray1 = new IntClass[Count];
while (r < 666000)
{
IntRandim F = new IntRandim(r, 0, Count);
timer.Start();
for (int i = 0; i < Count; i++)
{
IntClassArray1[i] = new IntClass(i);
}
// перемешаем массивfor (int i = 0; i < Count; i++)
{
int temp = F.Next();
int temp2 = F.Next();
IntClass temp3 = IntClassArray1[temp];
IntClassArray1[temp] = IntClassArray1[temp2];
IntClassArray1[temp2] = temp3;
}
// сортировка вставками
IntClassArray[0] = IntClassArray1[0];
for (int i = 1; i < Count; i++)
{
Int32 L = 0;
Int32 R = i-1;
IntClass Current=IntClassArray1[i];
int key = Current.value;
//int key = IntClassArray1[i].value;while (L < R)
{
int Midl = (L + R) >>1;
if (IntClassArray[Midl].value <= key)
L = Midl + 1;
else
R = Midl;
}
for (int j = i; j > L; j--)
IntClassArray[j] = IntClassArray[j - 1];
IntClassArray[L] = IntClassArray1[i];
}
_log.AppendText(string.Format(" Время={0}" + Environment.NewLine, timer.Finish()));
for (int i = 1; i < Count; i++)
if (IntClassArray[i].value != i)
{
_log.AppendText(string.Format(" i={0} <> {1}", i, IntClassArray[i].value));
return;
}
r *= 10;
}
}
Но такой вариант безбожно тормозит пропорционально квадрату занимаемой памяти с применением Add3.
public class Int32AsObjectInsertSort
{
public IntClass[] ar;
public Int32 count;
private Int32 temp;
private Int32 Midl;
public Int32AsObjectInsertSort(Int32 Size)
{
ar = new IntClass[Size];
count = 0;
}
private Int32 ПоискПоловиннымДелением(Int32 key)
{
Int32 i = 0;
Int32 j = count - 1;
while (i <= j)
{
Midl = (i + j) >>1;
if (ar[Midl].value > key) { j = Midl - 1; }
else if (ar[Midl].value < key) { i = Midl + 1; }
else { return i; }
}
return i;
}
public void Add3(IntClass value)
{
if (count == 0)
{
count = 1;
ar[0] = value;
}
else
{
Int32 index = ПоискПоловиннымДелением(value.value);
for (Int32 i = count; i > index; i--)
{
ar[i] = ar[i - 1];
}
ar[index] = value;
count++;
}
}
}
... << RSDN@Home 1.1.0 stable >>
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, mihailik, Вы писали:
M>>>volatile and MemoryBarrier() Brad Adams Blog S>> Спасибо. Нужно будет попобовать.
M>Я вот вчитался сегодня с утра. По-моему там какой-то немного другой барьер. Так ничего чётко Адамс не сакзал.
Естественно другой. Это hardware write barrier. Нужен для того, чтобы кэшы синхронизировать и делать flush processor pipeline, например.
А в начале речь шла о barrier как о технике применяемой в generational GCs, которая помогает разделять generations.
M>Вообще впервые узнал, что в C# есть слово "volatile".
Есть. volatile в C# работает почти как в C++, только в добавление чтение volatile переменных имеет acquire semantics, а запись — release semantics. Очень полезно, когда процессор может производить изменение порядка чтения/записи в память (reads & writes reordering).
Кстати дома то у меня видби и эти два варианта отрабатывают по разному, на работе провел тест с 1.1 картина и отрабатывают одинаково.
В видби они кое что подправили.
Интересно этот же тест прогнать на 2005.
... << RSDN@Home 1.1.0 stable >>
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>Здравствуйте, Serginio1, Вы писали:
S> Кстати дома то у меня видби и эти два варианта отрабатывают по разному, на работе провел тест с 1.1 картина и отрабатывают одинаково. S> В видби они кое что подправили. S> Интересно этот же тест прогнать на 2005.
А в чем разница?
... << RSDN@Home 1.1.3 beta 2 >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Serginio1, Вы писали:
S>>Здравствуйте, Serginio1, Вы писали:
S>> Кстати дома то у меня видби и эти два варианта отрабатывают по разному, на работе провел тест с 1.1 картина и отрабатывают одинаково. S>> В видби они кое что подправили. S>> Интересно этот же тест прогнать на 2005.
VD>А в чем разница?
Разница в том, что в 1.1 эти два алгоритма отрабатывают одинаково, то есть сортировка в одной процедуре и сортировка через добавление (Int32AsObjectInsertSort.Add3)
и writw barier и в обоих случаях.
В видби сортировка в в процедуре в 2 раза медленне чем на интах , но стабильно, при сортировке Int32AsObjectInsertSort.Add3 write barier остается и геометрическая прогрессия замедления от размера данных как в 1.1
... << RSDN@Home 1.1.0 stable >>
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>Здравствуйте, mihailik, Вы писали:
M>>Ещё немного из свежего блога:
M>>volatile and MemoryBarrier() Brad Adams Blog S> Спасибо. Нужно будет попобовать.
По указанной ссылке написано:
public static Singleton Value {
get {
if (Singleton.value == null) {
lock (syncRoot) {
if (Singleton.value == null) {
Singleton newVal = new Singleton();
// Insure all writes used to construct new value have been flushed.
System.Threading.Thread.MemoryBarrier();
Singleton.value = newVal; // publish the new value
}
}
}
return Singleton.value;
}
}
Почему System.Threading.Thread.MemoryBarrier();
Выполняется до Singleton.value = newVal;
а не после него, если мы боимся, что к моменту выполнения
другим потоком (lock (syncRoot) { if (Singleton.value == null) ...)
Singleton.value еще не будет еще присвоено?
Не логичнее ли высвободить кэш после присвоения?
Здравствуйте, kkav, Вы писали:
K>Здравствуйте, Serginio1, Вы писали:
S>>Здравствуйте, mihailik, Вы писали:
M>>>Ещё немного из свежего блога:
M>>>volatile and MemoryBarrier() Brad Adams Blog S>> Спасибо. Нужно будет попобовать.
K>По указанной ссылке написано: K>
K> public static Singleton Value {
K> get {
K> if (Singleton.value == null) {
K> lock (syncRoot) {
K> if (Singleton.value == null) {
K> Singleton newVal = new Singleton();
K> // Insure all writes used to construct new value have been flushed.
K> System.Threading.Thread.MemoryBarrier();
K> Singleton.value = newVal; // publish the new value
K> }
K> }
K> }
K> return Singleton.value;
K> }
K>}
K>
Там в одном из комментариев более правильная версия есть с двумя memory barriers.
K>Почему System.Threading.Thread.MemoryBarrier(); K>Выполняется до Singleton.value = newVal; K>а не после него, если мы боимся, что к моменту выполнения K>другим потоком (lock (syncRoot) { if (Singleton.value == null) ...) K>Singleton.value еще не будет еще присвоено? K>Не логичнее ли высвободить кэш после присвоения?
Допустим, делается так как ты предлагаешь. Тогда есть следующий вариант развития событий: первый поток входит в lock выделяет Singleton и инициализирует Singleton.value, но замещается вторым потоком прям перед вызовом MemoryBarrier(). Второй поток проверяет Singleton.value, его значение уже не null, и оно тут-же возвращается из функции. Далее второй поток пытается использовать тот объект Singleton, который он получил, но так как сконструированный объект Singleton еще находится в кэше первого потока (пусть эти потоки выполняются на разных процессорах), то второй поток, думая, что он обращается к памяти Singleton'а, на самом деле читает память, в которой еще мусор. Вот так.
Вопрос этот вообщем очень интересный. Почитай пост Vance Morrison'а (ссылка в блоге Брада) про моделирование поведения памяти с помощью перестановок чтения/записи. А так же про модель памяти CLR в блоге Chris Brumme'а.
Здравствуйте, alexkro, Вы писали:
A>Допустим, делается так как ты предлагаешь. Тогда есть следующий вариант развития событий: первый поток входит в lock выделяет Singleton и инициализирует Singleton.value, но замещается вторым потоком прям перед вызовом MemoryBarrier(). Второй поток проверяет Singleton.value, его значение уже не null, и оно тут-же возвращается из функции. Далее второй поток пытается использовать тот объект Singleton, который он получил, но так как сконструированный объект Singleton еще находится в кэше первого потока (пусть эти потоки выполняются на разных процессорах), то второй поток, думая, что он обращается к памяти Singleton'а, на самом деле читает память, в которой еще мусор. Вот так.
почему мусор? ИМХО, там должен быть 0 — поскольку сброс кэша еще не выполнен, а Singleton.value тоже находится в нем
а в оригинальном варианте есть немаленькая вероятность, что будет выделено несколько объектов singleton (по одному на каждый процессор )
Здравствуйте, Дарней, Вы писали:
Д>почему мусор? ИМХО, там должен быть 0 — поскольку сброс кэша еще не выполнен, а Singleton.value тоже находится в нем\]
Конечно, там не мусор. Другое дело, что конструктор Singleton'а тоже может что-то писать в память.
И тут-то начинается чехарда. В IA32, где Strong Memory Model все нормально.
У IA64 (и CRL) все плохо — Weak Memory Model, т.е процессор _не_обязан_ класть в память сначала
переменные, проинициализированные в конструкторе Singleton'а, а потом его самого. Пример:
public Singleton {
this.str = new string("blabla");
}
public static Singleton Value {
get {
if (Singleton.value == null) {
lock (syncRoot) {
if (Singleton.value == null) {
Singleton newVal = new Singleton();
Singleton.value = newVal;
}
}
}
return Singleton.value;
}
}
Тут у IA64 в какой-то момент времени Singleton.value уже перешел из кеша процессора в память,
а Singleton.str еще null!!! Хотя конструктор уже отработал
Такое может произойти, если
1. Процессоров больше чем один
2. Singleton.value и Singleton.str окажутся в разных страницах памяти.
3. Со страницей памяти Singleton.str работает другой процессор.
Вот этот самый другой процессор может словить Singleton.value != null и Singleton.str == null.
Д>есть немаленькая вероятность, что будет выделено несколько объектов singleton (по одному на каждый процессор )
Нулевая. Ну-ле-ва-я. Об этом позаботится
lock (syncRoot) {
}
[c#]
Замечу, что если переписать код вот так:
[c#]
public static Singleton Value {
get {
lock (syncRoot) {
if (Singleton.value == null) {
Singleton newVal = new Singleton();
Singleton.value = newVal;
}
}
return Singleton.value;
}
}
То все будет очень даже корректно и для IA64 и для IA32.
Здравствуйте, Дарней, Вы писали:
Д>Здравствуйте, alexkro, Вы писали:
A>>Допустим, делается так как ты предлагаешь. Тогда есть следующий вариант развития событий: первый поток входит в lock выделяет Singleton и инициализирует Singleton.value, но замещается вторым потоком прям перед вызовом MemoryBarrier(). Второй поток проверяет Singleton.value, его значение уже не null, и оно тут-же возвращается из функции. Далее второй поток пытается использовать тот объект Singleton, который он получил, но так как сконструированный объект Singleton еще находится в кэше первого потока (пусть эти потоки выполняются на разных процессорах), то второй поток, думая, что он обращается к памяти Singleton'а, на самом деле читает память, в которой еще мусор. Вот так.
Д>почему мусор? ИМХО, там должен быть 0 — поскольку сброс кэша еще не выполнен, а Singleton.value тоже находится в нем
Ты не понял модель памяти CLR. Запись в память для переменных, доступных глобально, является release. Значит присвоение Singleton.value сразу видимо для всех потоков. А вот сам объект — нет. Это я и имею в виду. На месте объекта для другого потока ещё сырая память — мусор.
Д>а в оригинальном варианте есть немаленькая вероятность, что будет выделено несколько объектов singleton (по одному на каждый процессор )
Нет. В оригинальном варианте тот же самый случай: Singleton.value проинициализирован, а сам объект — нет.
Здравствуйте, Блудов Павел, Вы писали:
БП>И тут-то начинается чехарда. В IA32, где Strong Memory Model все нормально.
Не тут то было. Strong memory model для одного процессора. А если у тебя их восемь: по четыре в hemisphere? Тут-то синхронизация кэшей начинает играть ту же роль, что и перестановка чтения/записи.
БП>У IA64 (и CRL) все плохо — Weak Memory Model, т.е процессор _не_обязан_ класть в память сначала
У CLR есть своя модель памяти. На IA64 она не будет weak.