Здравствуйте, fdn721, Вы писали:
F>Ох уж эти объясняторы...
F>На самом деле ни кто ни чего не переупорядочивает.
На самом деле переупорядочивает. В Pentium-Pro появился буфер переупорядочивания, тогда ещё размером в 40 мопов и теперь он решает в каком порядке выполнять мопы, а не ты.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, dsalodki, Вы писали:
D>Поясните пожалуйста самым простым способом, я гуглю, но что-то не понятно. Толи это позволяет отменить кеширование переменно, толи ещё что-то
Поправьте где неправ:
1. volatile в С# — какая-то шляпа
2. lock — вроде как ведет к Memory Barier, правда неявно
3. у нас есть Interlocked, которого хватает в 99%
Здравствуйте, syrompe, Вы писали:
S>Поправьте где неправ: S>1. volatile в С# — какая-то шляпа
Нет, оно работает так как должно. Штука очень полезная, особенно если ты шаришь память между между процессами (например с помощью MMF). Например, у тебя выделена память с помощью VirtualAlloc(), в которой ты сделал окно. Ты на неё смотришь через стуктуру (приводишь к указателю на структуру). Все поля этой структуры должны быть volatile — компилятор никогда не сможет угадать, кто и когда поменяет эту память. Фактически это указание компилятору, что к памяти имеет доступ кто-то ещё.
S>2. lock — вроде как ведет к Memory Barier, правда неявно
Да, это так.
S>3. у нас есть Interlocked, которого хватает в 99%
Нет: кому-то хватает, а кому-то нет. Interlocked в конечном счёте выставляет префикс lock на операции обращения к памяти. Это приодит к сигналу Lock на шине процессора при исполнение — производительность на таких штуках серьёзно деградирует.
В принципе любые примитивы синхронизации приводят к деградации производительности. Чем более они высокоуровневые — тем сильнее, хотя с походами в ядро им конечно не сравниться.
Насчёт Interlocked: в циклах ожидания нужно использовать процессорную инструкцию pause, но из C# этого сделать невозможно. Вот пример:
while (1 == Interlocked.CompareExchange(ref m_dwBusy, 1, 0))
{
//здесь должна быть pause
}
Т.е. я не знаю — по-моему это ещё не ушло в релиз.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Философ, Вы писали:
S>>3. у нас есть Interlocked, которого хватает в 99%
Ф>Нет: кому-то хватает, а кому-то нет. Interlocked в конечном счёте выставляет префикс lock на операции обращения к памяти. Это приодит к сигналу Lock на шине процессора при исполнение — производительность на таких штуках серьёзно деградирует.
Чтобы почувствовать эту "серьезную деградацию", на современном железе нужно дергать эти операции с частотой хотя бы в сотни тысяч, а лучше — миллионы раз в секунду. Соответственно, чтоб это стало видно на фоне работы полезного кода, он должен быть идеально вылизан — и не на уровне языка, а на уровне машинного кода.
Ф>В принципе любые примитивы синхронизации приводят к деградации производительности. Чем более они высокоуровневые — тем сильнее, хотя с походами в ядро им конечно не сравниться.
Какие высокоуровневые примитивы синхронизации обходятся без походов в ядро?
P>не-е-е-е, если вы намерено не запрограммировали и не выполнили сравнение в другом потоке или нити никогда не будет.
Ну да. Если ты намеренно не запрограммировал это на разных потоках, то барьер не нужен. Вообще, мне кажется, если программировать всё в одном потоке, то вообще можно не знать, что барьеры существуют. По крайней мере, в "обычных" языках и процессорах.
Здравствуйте, Философ, Вы писали:
Ф>Здравствуйте, samius, Вы писали:
S>>volatile как модификатор переменной — запрет на кэширование. volatile как инструкция — запрет на чтение/запись из кэша в конкретном месте, т.е. немного не о том в общем случае, но в частном (C# и .NET) volatile фактически генерирует вокруг обращений еще и барьеры. В языках, где volatile не генерирует барьеры, проще представить одно без другого.
Ф>volatile в шарпе не генерирует никаких барьеров.
Если так, то с помощью какоюй таблетки обеспечивается семантика, заявленная в спеке C# в общем случае, а не про Intel X86/X64?
A read of a volatile field is called a volatile read. A volatile read has “acquire semantics”; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence.
A write of a volatile field is called a volatile write. A volatile write has “release semantics”; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence.
Ф>Слово volatile запрещает оптимизации компялитора в отношении поля, например компилятор всегда будет обращаться памяти где лежит эта переменная, не кешируя её в регистре. Ещё: компилятор не исключит обращения к этой переменной при чтении. Ф>Новое значение в переменную компилятор будет писать там, где это написал программист, а не там где это оказалось удобно компилятору. Ф>Всё!
Ф>Больше volatile не делает ничего. Барьеры при обращении к нескольким volatile полям по-прежнему нужны.
С оговорками про конкретную платформу — да. А вообще есть другие мнения. А так же указание полного барьера в методах VolatileRead/Write.
Здравствуйте, samius, Вы писали:
Ф>>volatile в шарпе не генерирует никаких барьеров. S>Если так, то с помощью какоюй таблетки обеспечивается семантика, заявленная в спеке C# в общем случае, а не про Intel X86/X64?
с помощью Thread.VolatileRead() и Thread.VolatileWrite()
S>С оговорками про конкретную платформу — да. А вообще есть другие мнения. А так же указание полного барьера в методах VolatileRead/Write.
Фишка в том, что барьеры нужны там где они нужны: там где есть опасность перестановок — нужны, там где пофиг — не нужны.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Чтобы почувствовать эту "серьезную деградацию", на современном железе нужно дергать эти операции с частотой хотя бы в сотни тысяч, а лучше — миллионы раз в секунду.
И тем не менее достичь этого легко: даже Thread.SpinWait(Int32) используется частенько. Достичь 100К итераций ожидания не так сложно как тебе кажется.
ЕМ>Какие высокоуровневые примитивы синхронизации обходятся без походов в ядро?
Ну например SpinLock, ну а если повезёт, то например ManualResetEventSlim тоже может на ожидании без походов в ядро обойтись.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Философ, Вы писали:
Ф>Здравствуйте, samius, Вы писали:
Ф>>>volatile в шарпе не генерирует никаких барьеров. S>>Если так, то с помощью какоюй таблетки обеспечивается семантика, заявленная в спеке C# в общем случае, а не про Intel X86/X64?
Ф>с помощью Thread.VolatileRead() и Thread.VolatileWrite()
Разумеется, работа с volatile полями не приводит к задействованию этих методов. И, прямо в документации по предъявленным ссылкам, написано, что барьеры вставляются при необходимости.
S>>С оговорками про конкретную платформу — да. А вообще есть другие мнения. А так же указание полного барьера в методах VolatileRead/Write.
Ф>Фишка в том, что барьеры нужны там где они нужны: там где есть опасность перестановок — нужны, там где пофиг — не нужны.
А разве кто-то утверждал обратное? Что они не нужны там, где нужны и нужны там, где не нужны?
Здравствуйте, samius, Вы писали:
S>...И, прямо в документации по предъявленным ссылкам, написано, что барьеры вставляются при необходимости.
А в коде BCL написано что, там безусловно вставляется барьер, без всяких "по необходимости". Сходи и посмотри.
S>А разве кто-то утверждал обратное? Что они не нужны там, где нужны и нужны там, где не нужны?
В этой теме кто-то говорил, что для volatile полей генерируются барьеры. На деле же барьеры ты должен вставлять сам.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Философ, Вы писали:
Ф>Здравствуйте, samius, Вы писали:
S>>...И, прямо в документации по предъявленным ссылкам, написано, что барьеры вставляются при необходимости.
Ф>А в коде BCL написано что, там безусловно вставляется барьер, без всяких "по необходимости". Сходи и посмотри.
Я об этом пару сообщений назад написал.
Здравствуйте, Философ, Вы писали:
Ф>И тем не менее достичь этого легко: даже Thread.SpinWait(Int32) используется частенько. Достичь 100К итераций ожидания не так сложно как тебе кажется.
Ну да, если вставлять, где ни попадя, то легко. Если хоть немного с умом (то есть, сообразуясь с реальной потребностью), то достаточно сложно.
ЕМ>>Какие высокоуровневые примитивы синхронизации обходятся без походов в ядро?
Ф>Ну например SpinLock
Он только с виду "высокоуровневый". Внутри там примитивный цикл из InterlockedCompareExchange (которая раскрывается в команду CmpXchg), InterlockedIncrement/InterlockedDecrement (которые раскрываются в lock xadd), или чего-то подобного. Реально же высокоуровневые примитивы, подразумевающие ожидание с переключением потоков, без походов в ядро не реализуемы.
Ф>если повезёт, то например ManualResetEventSlim тоже может на ожидании без походов в ядро обойтись.
И это на той же технике, как и критические секции со spin count. Как раз костыли для тех, кто их использует бездумно, "по учебнику".
Здравствуйте, Философ, Вы писали:
ЕМ>>Какие высокоуровневые примитивы синхронизации обходятся без походов в ядро?
Ф>Сейчас глянул сорцы CLR — Monitor.Enter() в конечном счёте тоже может без походов в ядро обойтись.
Из каких соображений Вы причисляете такие средства к "высокоуровневым"?
Здравствуйте, Философ, Вы писали:
ЕМ>>Из каких соображений Вы причисляете такие средства к "высокоуровневым"?
Ф>Из тех соображений, что иногда синхронизация делается вообще без каких-либо примитивов, пример рукопашной синхронизации.
То есть, если программа использует InterlockedCompareExchange, что фактически просто вставляет команду CmpXchg, то это "низкоуровневое" средство, а если вызывает некую библиотечную функцию, в которую завернута та же команда, то средство враз становится "высокоуровневым"?
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>То есть, если программа использует InterlockedCompareExchange, что фактически просто вставляет команду CmpXchg, то это "низкоуровневое" средство, а если вызывает некую библиотечную функцию, в которую завернута та же команда, то средство враз становится "высокоуровневым"?
Ну в общем-то да: даже ReaderWriterLock в конечном счёте ведёт к CompareExchange.
Признаю, что пожалуй ты прав: SpinLock не стоило относить к высокоуровневым.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Философ, Вы писали:
Ф>В конечном машинном коде будет mfence или как вариант одна из sfence/lfence инструкций — они запрещают переупорядочивание доступа к памяти вокруг себя.
sfence/lfence/mfence — это малость для другого. В машинный код для x86/x64 практически всегда вставляется инструкция типа xchg, xadd или что-то подобное.
Здравствуйте, okman, Вы писали:
O>sfence/lfence/mfence — это малость для другого. В машинный код для x86/x64 практически всегда вставляется инструкция типа xchg, xadd или что-то подобное.
Интересно, откуда инфа? Можно какие-нибудь доказательства, типа вот такой ссылки, ну или вот такой, или дизасм получающегося кода, или объяснение того, что именно будет делать функция типа
void Foo(){
Thread.MemoryBarrier();
}
Я ОООчень бы хотел узнать, почему вы так думаете, откуда вы это взяли и где тут логика.
ЗЫ: прежде чем отвечать сходите по приведённым ссылкам.