И снова про MemoryBarrier... Кэшируем объекты в Dictionary
От: MxMsk Португалия  
Дата: 12.01.11 14:26
Оценка:
Приветствую! Вопросов два.

Первый. Как же все-таки правильно, "кеш" или "кэш"? Помнится в ВУЗе учили, что кеш. Очень хорошо запомнил. А потом гляжу, так продвинутые пишут "кэш". Гуглохром упорно делает вид, что вообще не знает оба слова. Это, так сказать, лирическое вступление, чтобы создать настроение

Второй (и самый важный!). Снова думаю о чудесном расчудесном MemoryBarrier. Перечитал веточки здесь, маны поглядел — уже думаю за бутылку браться, так меня многопоточное разводилово клинит, что хочется напиться и забыться. Но нет! Путь из формошлепщика в гуру тяжел — будем бороцца!

Итак, есть такой код:
public static class PropertyChangedHelper
{
    private static readonly Dictionary<string, PropertyChangedEventArgs> _EventArgsCache = new Dictionary<string, PropertyChangedEventArgs>();

    public static PropertyChangedEventArgs GetEventArgs(string propertyName)
    {
        PropertyChangedEventArgs eventArgs;
        bool found = PropertyChangedHelper._EventArgsCache.TryGetValue(propertyName, out eventArgs);

        if (!found)
        {
            lock (PropertyChangedHelper._EventArgsCache)
            {
                if (!PropertyChangedHelper._EventArgsCache.TryGetValue(propertyName, out eventArgs))
                {
                    eventArgs = new PropertyChangedEventArgs(propertyName);
                    PropertyChangedHelper._EventArgsCache.Add(propertyName, eventArgs);
                }
            }
        }

        return eventArgs;
    }
}


Так вот я задумался: есть ли здесь необходимость в барьерах? Может ли так случиться, что из-за переупорядочивания, объект eventArgs окажется в словаре до того, как его свойство PropertyName получит нужное значение? Это в свою очередь может привести к тому, что другой поток, увидит в словаре данные с тем же ключом, не увидит никакого барьера и вернет еще невалидный объект. В связи с этим подумалось, может нужно так?

public static class PropertyChangedHelper
{
    private static readonly Dictionary<string, PropertyChangedEventArgs> _EventArgsCache = new Dictionary<string, PropertyChangedEventArgs>();

    public static PropertyChangedEventArgs GetEventArgs(string propertyName)
    {
        PropertyChangedEventArgs eventArgs;
        bool found = PropertyChangedHelper._EventArgsCache.TryGetValue(propertyName, out eventArgs);

        if (found)
        {
            Thread.MemoryBarrier();
        }
        else        
        {
            lock (PropertyChangedHelper._EventArgsCache)
            {
                if (!PropertyChangedHelper._EventArgsCache.TryGetValue(propertyName, out eventArgs))
                {
                    eventArgs = new PropertyChangedEventArgs(propertyName);
                    Thread.MemoryBarrier();
                    PropertyChangedHelper._EventArgsCache.Add(propertyName, eventArgs);
                }
            }
        }

        return eventArgs;
    }
}



Но здесь мне кажется первый барьер точно не нужен. Да, я понимаю, что барьер — это игра двух. Это я запомнил Но ведь eventArgs окажется в словаре только после прохождения барьера, который должен обеспечить полную инициализацию объекта.

А может я вообще здесь все нагнал. Просвятите, плиз!
Re: И снова про MemoryBarrier... Кэшируем объекты в Dictiona
От: TK Лес кывт.рф
Дата: 12.01.11 14:47
Оценка: +2
Здравствуйте, MxMsk, Вы писали:

MM>А может я вообще здесь все нагнал. Просвятите, плиз!


Из MSDN: Dictionary can support multiple readers concurrently, as long as the collection is not modified.. У вас это не так.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[2]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: MxMsk Португалия  
Дата: 12.01.11 14:51
Оценка:
Здравствуйте, TK, Вы писали:

MM>>А может я вообще здесь все нагнал. Просвятите, плиз!

TK>Из MSDN: Dictionary can support multiple readers concurrently, as long as the collection is not modified.. У вас это не так.
Это намек на то, что TryGetValue незаконно звать без lock?
Re[3]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: TK Лес кывт.рф
Дата: 12.01.11 14:57
Оценка: 10 (1)
Здравствуйте, MxMsk, Вы писали:

MM>>>А может я вообще здесь все нагнал. Просвятите, плиз!

TK>>Из MSDN: Dictionary can support multiple readers concurrently, as long as the collection is not modified.. У вас это не так.
MM>Это намек на то, что TryGetValue незаконно звать без lock?

Конечно. Или используйте HashTable.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[4]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: MxMsk Португалия  
Дата: 12.01.11 15:00
Оценка:
Здравствуйте, TK, Вы писали:

MM>>Это намек на то, что TryGetValue незаконно звать без lock?

TK>Конечно. Или используйте HashTable.
А может лучше ConcurrentDictionary&lt;TKey, TValue&gt;?

И потом, вопрос про барьер все-равно остается. Если допустить, что TryGetValue можно дернуть.
Re: И снова про MemoryBarrier... Кэшируем объекты в Dictiona
От: Sinix  
Дата: 12.01.11 15:24
Оценка: 20 (2) +2
Здравствуйте, MxMsk, Вы писали:

MMПросвятите, плиз!

return new PropertyChangedEventArgs(propertyName);

И не мучать экономией на копейках ни себя, ни окружающих.

Если инициализация объекта настолько тяжела —
lock(cacheLockKey)
{
  return cache.GetOrCreate(propertyName, ()=>new PropertyChangedEventArgs(propertyName));
}

Реализацию extension-метода GetOrCreate приводить, думаю, не надо

C memory barrier возиться здесь абсолютно бессмысленно — оно надо для весьма специфичных сценариев с последовательной инициализацией неявно зависящих друг от друга полей. На весьма экзотических платформах.
Если коротко — http://www.codeproject.com/KB/tips/MemoryBarrier.aspx

из закладок:
http://www.bluebytesoftware.com/blog/2007/11/10/CLR20MemoryModel.aspx
http://www.bluebytesoftware.com/blog/2009/02/02/CCompilerWarningCS0420ByrefsToVolatiles.aspx
http://www.bluebytesoftware.com/blog/2008/06/13/VolatileReadsAndWritesAndTimeliness.aspx

http://www.albahari.com/threading/part4.aspx

http://stackoverflow.com/questions/2158001/lockless-threading-question
http://stackoverflow.com/questions/2005211/when-to-use-lock-vs-memorybarrier-in-net
http://stackoverflow.com/questions/1330590/when-to-use-volatile-or-thread-memorybarrier-in-threadsafe-locking-code-c

http://blogs.msdn.com/b/brada/archive/2004/05/12/130935.aspx

http://www.codeproject.com/KB/tips/MemoryBarrier.aspx
http://msdn.microsoft.com/en-us/magazine/cc163715.aspx

Также см
http://www.bluebytesoftware.com/books/winconc/winconc_book_resources.html
и последнее издание Рихтера.
Re[5]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: Jolly Roger  
Дата: 12.01.11 16:11
Оценка: -1
Здравствуйте, MxMsk, Вы писали:

MM>И потом, вопрос про барьер все-равно остается. Если допустить, что TryGetValue можно дернуть.


Не нужен. Конструктор — функция с побочным эффектом хотя-бы потому, что может генерить исключение, поэтому переупорядочивание через него невозможно.
"Нормальные герои всегда идут в обход!"
Re[2]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: Jolly Roger  
Дата: 12.01.11 16:30
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Если коротко — http://www.codeproject.com/KB/tips/MemoryBarrier.aspx


Хм, фраза оттуда

On IA64, cache coherency on write operation is not automatic and therefore explicit memory barriers are needed for write operations to flush to other core caches (this is where volatile comes handy).


вызывают некоторые сомнения в компетентности автора
"Нормальные герои всегда идут в обход!"
Re[3]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: Sinix  
Дата: 12.01.11 16:39
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>вызывают некоторые сомнения в компетентности автора

Ага, но для вводной статьи — чисто показать грабли — сойдёт
Re[2]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: MxMsk Португалия  
Дата: 12.01.11 17:12
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, MxMsk, Вы писали:


S>MMПросвятите, плиз!

S>return new PropertyChangedEventArgs(propertyName);
S>И не мучать экономией на копейках ни себя, ни окружающих.
Тоже прихожу к такому варианту. Тем более, что когда поставил MemoryBarrier, профайлер сразу ткнул пальцем, мол, долго работаем! А над кешированием я задумался из-за постов типа этого. Кстати, лок там на публичном типе доставляет

S>C memory barrier возиться здесь абсолютно бессмысленно — оно надо для весьма специфичных сценариев с последовательной инициализацией неявно зависящих друг от друга полей. На весьма экзотических платформах.

S>Если коротко — http://www.codeproject.com/KB/tips/MemoryBarrier.aspx
За ссылочки спасибо. Только я почти все их читал ранее. И каждый раз, когда я решу, что всё понял, очередная статья всё рушит. Ну, вот например у Албахари про volatile. Он пишет, что MSDN ошибается. Я ему верю, а потом читаю это
Автор: drol
Дата: 26.10.09
(см. абзац с "автор немного увлекся"). Что касается экзотичности, то простой пример из того же Албахари (Do We Really Need Locks and Barriers?) спокойно проявляется на моем рабочем Core 2 Duo.

S>и последнее издание Рихтера.

Ну, это святое
Re[6]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: MxMsk Португалия  
Дата: 12.01.11 17:16
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Не нужен. Конструктор — функция с побочным эффектом хотя-бы потому, что может генерить исключение, поэтому переупорядочивание через него невозможно.

Означает ли это, что я всегда могу быть уверен в полной инициализации объекта, если у меня есть ссылка на него?
И хотелось бы понять, как "побочность" влияет на переупорядочивание?
Re[7]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: Jolly Roger  
Дата: 12.01.11 18:05
Оценка: 10 (1)
Здравствуйте, MxMsk, Вы писали:

MM>Означает ли это, что я всегда могу быть уверен в полной инициализации объекта, если у меня есть ссылка на него?


Смотря что понимать под инициализацией. Например, такая инициализация

var o = new Some(x){field1 = 33}


может привести к неожиданным результатам. Чтобы быть уверенным, нужно использовать стандартные средства, в данном случае — любой доступ к словарю только внутри честного лока, иначе необходимо анализировать каждый случай отдельно.

MM>И хотелось бы понять, как "побочность" влияет на переупорядочивание?


В данном случае конструктор может возбудить исключение, что будет означать несоздание объекта. В таких условиях добавление ссылки на объект в словарь до вызова конструктора будет нарушением последовательности операций, т.е. перестановка может привести к непредсказуемому результату. Это и есть побочный (side) эффект.
"Нормальные герои всегда идут в обход!"
Re[8]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: MxMsk Португалия  
Дата: 12.01.11 18:51
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Смотря что понимать под инициализацией. Например, такая инициализация

JR>var o = new Some(x){field1 = 33}
JR>может привести к неожиданным результатам. Чтобы быть уверенным, нужно использовать стандартные средства, в данном случае — любой доступ к словарю только внутри честного лока, иначе необходимо анализировать каждый случай отдельно.
Такую инициализацию я, конечно, не имел ввиду. Меня интересовал только конструктор.

MM>>И хотелось бы понять, как "побочность" влияет на переупорядочивание?

JR>В данном случае конструктор может возбудить исключение, что будет означать несоздание объекта. В таких условиях добавление ссылки на объект в словарь до вызова конструктора будет нарушением последовательности операций, т.е. перестановка может привести к непредсказуемому результату. Это и есть побочный (side) эффект.
Но процессор ведь этого не понимает. Получается, подобное поведение нам обеспечивает компилятор?
Re[9]: И снова про MemoryBarrier... Кэшируем объекты в Dicti
От: Jolly Roger  
Дата: 13.01.11 03:01
Оценка: -1
Здравствуйте, MxMsk, Вы писали:

MM>Но процессор ведь этого не понимает. Получается, подобное поведение нам обеспечивает компилятор?


Конечно. Но и volatile, и barrier — инструкции для компилятора. Для процессора существуют зависимости по данным и управлению. Например, имеем вызов функции int SomeFunc(int parameter) и ассемблерный код её вызова

mov ecx, dword ptr [somevalue] //готовим параметр
call SomeFunc // вызов функции


Если-бы процессор имел возможность переставить эти две асмовые строки местами, то у нас никогда не работали-бы все вызовы функций и методов, но они работают Следовательно, для процессора передача управления по адресу — точка, через которую он не может переносить другие операции, т.е. имеет место зависимость по управлению.
"Нормальные герои всегда идут в обход!"
Re: И снова про MemoryBarrier... Кэшируем объекты в Dictiona
От: Jolly Roger  
Дата: 13.01.11 03:54
Оценка:
Здравствуйте, MxMsk, Вы писали:

MM>Первый. Как же все-таки правильно, "кеш" или "кэш"?


Правильно "кыш"
"Нормальные герои всегда идут в обход!"
Re: И снова про MemoryBarrier... Кэшируем объекты в Dictiona
От: _d_m_  
Дата: 13.01.11 07:15
Оценка:
Здравствуйте, MxMsk, Вы писали:

MM>Приветствую! Вопросов два.


MM>Первый. Как же все-таки правильно, "кеш" или "кэш"? Помнится в ВУЗе учили, что кеш. Очень хорошо запомнил. А потом гляжу, так продвинутые пишут "кэш". Гуглохром упорно делает вид, что вообще не знает оба слова. Это, так сказать, лирическое вступление, чтобы создать настроение


праильно кэш.
иначе: кеш, флеш и т.п.
Re[10]: И снова про MemoryBarrier... Кэшируем объекты в Dict
От: MxMsk Португалия  
Дата: 13.01.11 08:02
Оценка: +1
Здравствуйте, Jolly Roger, Вы писали:

JR>Если-бы процессор имел возможность переставить эти две асмовые строки местами, то у нас никогда не работали-бы все вызовы функций и методов, но они работают Следовательно, для процессора передача управления по адресу — точка, через которую он не может переносить другие операции, т.е. имеет место зависимость по управлению.

А если компилятор решит заинлайнить и конструктор и добавление в словарь?
Re[11]: И снова про MemoryBarrier... Кэшируем объекты в Dict
От: Jolly Roger  
Дата: 14.01.11 08:02
Оценка:
Здравствуйте, MxMsk, Вы писали:

MM>А если компилятор решит заинлайнить и конструктор и добавление в словарь?


А если заинлайнит, то будут проблемы Я ведь уже говорил

"любой доступ к словарю только внутри честного лока, иначе необходимо анализировать каждый случай отдельно."


"Нормальные герои всегда идут в обход!"
Re[12]: И снова про MemoryBarrier... Кэшируем объекты в Dict
От: MxMsk Португалия  
Дата: 14.01.11 08:10
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>А если заинлайнит, то будут проблемы Я ведь уже говорил

JR>"любой доступ к словарю только внутри честного лока, иначе необходимо анализировать каждый случай отдельно."
Ну, давай представим, что он заинлайнит. Тогда нам понадобятся барьеры, как я написал в первом посте? Я хочу выяснить, правильно ли я понимаю цель барьеров.
Re[13]: И снова про MemoryBarrier... Кэшируем объекты в Dict
От: Jolly Roger  
Дата: 14.01.11 10:04
Оценка: -1
Здравствуйте, MxMsk, Вы писали:

MM>Ну, давай представим, что он заинлайнит. Тогда нам понадобятся барьеры, как я написал в первом посте? Я хочу выяснить, правильно ли я понимаю цель барьеров.


Если заинлайнит, то могут понадобиться. Смысл барьеров вобщем-то прост — это точка, через которую инструкции обращения к памяти не могут быть перемещены. По сути, директива компилятору, и по хорощему в результирующем коде он не должен-бы присутствовать в виде call, но присутствует. Если целевая платформа имеет соответствующие средства на уровня процессора, то компилятор может при необходимости задействовать эти средства, а если, как x86, не имеет, то всё на уровне компилятора и ограничивается. Могут-ли быть инструкции переупорядочены, можно определить относительно просто. Нужно посмотреть, может-ли такое переупорядочивание повлиять на результат работы однопоточного варианта, т.е. при исполнении кода одним потоком. Если какое-то переупорядочивание не повлияет на однопоточный результат, то такая перестановка в принципе возможно, и теперь уже смотрим, может-ли оно испортить результат в случае многопоточности, и при необходимости боремся с ним с помощью барьера.

Вот если рассмотреть Ваш исходный пример. Во-первых, методы, генерирующие исключения, не инлайнятися. Во-вторых, если переставить местами инициализацию объекта и добавление его в словарь, то это может поломать логику даже в однопоточном случае. Отсюда и вывод, что здесь и без барьера последовательность выполнения сохранится. Конечно, можно возразить, что, дескать, в будущем могут начать инлайнить методы с генерацией исключений, но тут уж Вам придётся определяться самому.
"Нормальные герои всегда идут в обход!"
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.