Re[4]: Многопоточность
От: MadHuman Россия  
Дата: 22.12.20 09:13
Оценка:
Здравствуйте, _NN_, Вы писали:

Ф>>>volatile запрещает оптимизации компилятора в отношении поля. В ином случае Disposed может вернуть false там, где должен быть true, потому что false может "застрять" в регистре, куда ранее была зачитана ячейка памяти.

MH>>
MH>>internal bool Disposed => _disposed != 0;
MH>>


MH>>когда будет вызван гетер для Disposed — вариантов кроме как прочитать _disposed из памяти нет. не будет он из регистра читаться.

_NN>А как же инлайнинг и оптимизация?
_NN>Разве есть гарантия, что компилятор и джит обязаны в случае свойства всегда читать из памяти?

здесь
Автор: MadHuman
Дата: 22.12.20
Re[7]: Многопоточность
От: Философ Ад http://vk.com/id10256428
Дата: 22.12.20 09:24
Оценка:
Здравствуйте, MadHuman, Вы писали:

Ф>>а завтра может быть пара из mov и cmp. Например потому, что это другой процессор, и там джиттер работает немного иначе.

MH>mov — всё равно из памяти будет читать
MH>то есть другого варианта нет
MH>дело не в том как джиттер работает. дело в том что при передаче управления в метод-геттера в теле метода надо читать филд, из памяти.

mov будет читать из памяти — других вариантов у него нет, но из ряда
mov -> cmp
mov -> cmp
mov -> cmp

можно исключить лишние мувы. Сам метод может быть заинлайнет, и тогда всё будет происходить не в get_IsDisposed, а в самом пользовательском методе, где как раз может быть поле для таких оптимизаций.


MH>
MH>internal bool Disposed => _disposed != 0;
MH>

MH>этот метод не жирный. здесь единственный случай чтения поля, и его можно только прочитать из памяти. не тот случай когда нужен volatile.

Проблема не в этом методе, а в том, что для этого поля оптимизации никто не запрещал.

MH>более того — если посмотреть в целом на ситуацию, то volatile не нужен. т.к. даже если мы гарантированно прочитали память, то в следующий момент ( может даже ещё до выхода из геттера) _disposed может оказаться выставленным,

MH>и вызывающая сторона ошибочно посчитает что ещё не диспозед, хотя уже диспозед. то есть volatile никак не предотвратит на 100% вызывающую Disposed сторону от ошибочного действия.

Угу, а потом ты будешь удивляться, что по логам пы коммит для транзакции послали, а транзакция куда-то пропала, хотя на самом деле никуда мы ничего не послали, а в этот момент сокет кто-то задиспозил.
Понимаешь, вовремя кинутое исключение ObjectDisposedException может тебе сократить неделю отладки.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[7]: Многопоточность
От: Sharov Россия  
Дата: 22.12.20 10:47
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>этот метод не жирный. здесь единственный случай чтения поля, и его можно только прочитать из памяти. не тот случай когда нужен volatile.


А почему только из памяти, какие гарантии?
Кодом людям нужно помогать!
Re[8]: Многопоточность
От: MadHuman Россия  
Дата: 22.12.20 11:04
Оценка: +1
Здравствуйте, Философ, Вы писали:


MH>>более того — если посмотреть в целом на ситуацию, то volatile не нужен. т.к. даже если мы гарантированно прочитали память, то в следующий момент ( может даже ещё до выхода из геттера) _disposed может оказаться выставленным,

MH>>и вызывающая сторона ошибочно посчитает что ещё не диспозед, хотя уже диспозед. то есть volatile никак не предотвратит на 100% вызывающую Disposed сторону от ошибочного действия.

Ф>Угу, а потом ты будешь удивляться, что по логам пы коммит для транзакции послали, а транзакция куда-то пропала, хотя на самом деле никуда мы ничего не послали, а в этот момент сокет кто-то задиспозил.

Ф>Понимаешь, вовремя кинутое исключение ObjectDisposedException может тебе сократить неделю отладки.
согласен. но в том-то и дело, что в данном примере топик-стартера нет 100% гарантии, что после того как из Disposed получили false (даже не заинлайнёнными и/или с volatile для _disposed)
сокет всё ещё не задиспозен, и описанная вами ситуация всё равно может повториться.
то есть volatile возможно может уменьшить вероятность такой ситуации (и ещё вопрос насколько), но никак не исключит её.
Отредактировано 22.12.2020 11:07 MadHuman . Предыдущая версия .
Re[8]: Многопоточность
От: MadHuman Россия  
Дата: 22.12.20 11:06
Оценка:
Здравствуйте, Sharov, Вы писали:

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


MH>>этот метод не жирный. здесь единственный случай чтения поля, и его можно только прочитать из памяти. не тот случай когда нужен volatile.


S>А почему только из памяти, какие гарантии?

а откуда ещё (при условии что метод не заинлайнен)? есть какие-то предположения откуда, кроме как из памяти?
Re[4]: Многопоточность
От: Sharov Россия  
Дата: 22.12.20 11:07
Оценка:
Здравствуйте, vlp, Вы писали:

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


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


VC>>>Прикольная задачка. Дело думаю в том что Disposed может переключится из false в true а обратно — нет.


S>>Ничего прикольного, и даже баг по-хорошему. Поскольку вызывать interlocked.read дороговато для когерентности кэшей, а это бы вызывалось для каждого вызова(проверка на dispose), то решили это убрать, один хрен будет исключение(скорее всего ObjectDisposedException). Но почему volatile не добавить :xz .

vlp>а чем принципиально здесь поможет volatile?

Судя по сообщением выше, действительно ничем. А вообще,если к переменной возможен доступ из нескольких потоков, то
сама же ms рекомендует этой переменной добавлять модификатор volatile.
Кодом людям нужно помогать!
Re[4]: Многопоточность
От: romangr Россия  
Дата: 22.12.20 11:12
Оценка: +2
Здравствуйте, Философ, Вы писали:

Ф>Такие гарантии может дать только volatile, т.е. запрет оптимизаций. В ином случае только на везение можно рассчитывать. В большинстве случае везёт. Учитывай, что геттер Disposed вполне может быть заинлайнен.


Гарантий, которые дает volatile недостаточно, т.к. между инструкциями cmp [ecx + offset], 1 и последующей за ней je/jne запросто может случиться context switch.
И пока этот поток спит, параллельный поток устанавливает _isdisposed=1. После чего первый поток просыпается и выбирает не ту ветку, т.к. в регистре флагов сохранено устаревшее значение.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[2]: Многопоточность
От: Sharov Россия  
Дата: 22.12.20 11:19
Оценка:
Здравствуйте, vlp, Вы писали:

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

vlp> ?
_NN>>Не нужен ли где-нибудь volatile, Volatile.Read или Interlocked.Read ?

vlp>Атомарная тут только запись, чтение теоретически может приводить к stale read и в этом случае не будет брошено исключение когда dispose вызван. Это в принципе нестрашно, т.к. в этом случае все равно код нормально работать уже не будет.


vlp>Interlocked.Read не сделан осознанно, чтобы не замедлять код. Dispose вызывается редко, в отличие от остальных операций на сокетах.


А нет ли здесь экономии на спичках? Т.е. мы не гоняем лишний раз когерентность кэшей для n потоков,
но при этом когда мы действительно освободим сокет, то n-1 поток обломятся на стадии системных вызовов ОС,
потеряв при этом не малое кол-во времени на обращении к той же памяти, при учете возможный прерываний отсутствующих
страниц VM и т.п. накладных расходов?

PS: Блин, ладно, удачно описался, и сам себе на вопрос и ответил. Оставлю как есть.
Действительно, проблемы тут нету, ибо Interlocked.CompareExchange как раз когеретность кэшей и вызывает, т.е.
все disposed поля у всех потоков будут одинаковы. Я ошибся в том, что Interlocked.Read также инициирует
когерентность кэшей, чего по идее может и не быть, это же чтение, а не запись. Т.е. Interlocked.CompareExchange
сделает disposed поле в кэше для всех одинаковым.

PPS: Блин, все-таки и вправду ошибка.А если disposed читается из регистра, а не из, хотя бы кэша? Т.е. вроде бы volatile
должно хватить, не говоря о Interlocked.Read...Так что, кмк, мой первоначальный вопрос в силе.
Кодом людям нужно помогать!
Re[9]: Многопоточность
От: Sharov Россия  
Дата: 22.12.20 11:21
Оценка:
Здравствуйте, MadHuman, Вы писали:

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


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


MH>>>этот метод не жирный. здесь единственный случай чтения поля, и его можно только прочитать из памяти. не тот случай когда нужен volatile.


S>>А почему только из памяти, какие гарантии?

MH>а откуда ещё (при условии что метод не заинлайнен)? есть какие-то предположения откуда, кроме как из памяти?

А если метод заинлайнен, то регистр, верно? А если нет, то регистр исключен? Я, честно, сам не до конца понимаю.\
Т.е.если инлайна нет, то почему не возможно чтение из регистра?
Кодом людям нужно помогать!
Re[10]: Многопоточность
От: Философ Ад http://vk.com/id10256428
Дата: 22.12.20 11:40
Оценка: 14 (1) +1
Здравствуйте, Sharov, Вы писали:

S>>>А почему только из памяти, какие гарантии?

MH>>а откуда ещё (при условии что метод не заинлайнен)? есть какие-то предположения откуда, кроме как из памяти?

S>А если метод заинлайнен, то регистр, верно? А если нет, то регистр исключен? Я, честно, сам не до конца понимаю.\

S>Т.е.если инлайна нет, то почему не возможно чтение из регистра?

Потому что функция генериутеся так, чтобы не делать никаких предположений о состоянии регистров. В случае экземплярного метода первый неявный параметр — this. Он передаётся в регистре rcx или ecx (x64 и x86 соответственно). Обращение к полям внутри метода — [rcx+FieldOffset], например:
cmp     byte ptr [rcx+8],0


Если функция заинлайнена, то в коде могут быть предположения о том, что находится в регистрах.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[10]: Многопоточность
От: MadHuman Россия  
Дата: 22.12.20 11:42
Оценка: +1
Здравствуйте, Sharov, Вы писали:

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


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


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


MH>>>>этот метод не жирный. здесь единственный случай чтения поля, и его можно только прочитать из памяти. не тот случай когда нужен volatile.


S>>>А почему только из памяти, какие гарантии?

MH>>а откуда ещё (при условии что метод не заинлайнен)? есть какие-то предположения откуда, кроме как из памяти?

S>А если метод заинлайнен, то регистр, верно?

да, если метод заинлайнен и ранее в нём было чтение этого филда, то вот в этом случае возможна оптимизация, что прочитанное его значение
запомнится в переменной-регистре и последующие чтения будут оттуда.
но в отдельно взятом методе, 1-е чтение филда всегда будет из памяти. потому что других вариантов нет)
Re[3]: Многопоточность
От: vlp  
Дата: 22.12.20 18:39
Оценка:
Здравствуйте, karbofos42, Вы писали:

K>ну, и не делали бы вовсе тогда проверку

проверка работает хорошо за исключением весьма и весьма редких случаев. На практике эти случаи возникают очень редко. Попробуйте на практике добиться того, чем тут всех пугают в этом треде — я уверен, что у вас не получится, а ObjectDisposedException вы будете получать стабильно. Профит для разработчика очевиден.
Re[7]: Многопоточность
От: vlp  
Дата: 22.12.20 18:48
Оценка:
Здравствуйте, Философ, Вы писали:

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


Ф>>>volatile это инструкция компилятору "не оптимизировать обращение к полю". Из-за таких оптимизаций чтение пожет быть "оптимизировано".


vlp>>Как именно нужно написать код, чтобы без volatile значение поля disposed было stale из-за "оптимизаций"? Этого и без volatile не происходит. Чтобы происходило нужна не одна проверка в начале функции (она точно из памяти читает), а хотя бы цикл с проверкой. Очень хотелось бы увидеть контрпример в виде кода и результаты дизассемблера.


Ф>Примеры дизассемблера тебе ничего не дадут: сегодня это один пример, а завтра версия джиттера поменяется.

Я просил пример того, как прямо сейчас, с текущей версией компилятора и джиттера, возможна ситуация, которой тут всех пугают и как volatile в ней помогает.
Тут, судя по ответам, в треде одни философы-теоретики, примеров нет.

>Для того, чтобы зачитанное значение могло застрять в регистре, достаточно несколько подряд вызовов, дёргающих ThrowIfDisposed(), например:


Ф>Connect()

Ф>Read()
Ф>Send()

Покажите скомпилированный код + дизассемблер кода, который в этом случае будет зачем-то записывать значение disposed в регистр и его потом переиспользовать. На практике и без volatile этого не делается, вместо этого значение всегда читается из памяти (почему-объяснять относительно долго)/

Ф>Такие вещи стреляют редко, но если стреляют, то хоть вешайся.

Чтобы это выстрелило, надо использовать disposed сокет. А race condition дебагать действительно тяжело, да.
Re[5]: Многопоточность
От: vlp  
Дата: 22.12.20 18:53
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Судя по сообщением выше, действительно ничем. А вообще,если к переменной возможен доступ из нескольких потоков, то

S>сама же ms рекомендует этой переменной добавлять модификатор volatile.
Ну так не надо слепо следовать всем рекомендациям, не понимая их сути. В ряде контекстов эта рекомендация не имеет смысла, а иногда даже вредит.
Re[8]: Многопоточность
От: Философ Ад http://vk.com/id10256428
Дата: 22.12.20 19:52
Оценка:
Здравствуйте, vlp, Вы писали:

Ф>>Примеры дизассемблера тебе ничего не дадут: сегодня это один пример, а завтра версия джиттера поменяется.

vlp>Я просил пример того, как прямо сейчас, с текущей версией компилятора и джиттера, возможна ситуация, которой тут всех пугают и как volatile в ней помогает.
vlp>Тут, судя по ответам, в треде одни философы-теоретики, примеров нет.

Зачем тебе реальный пример? Тебя теоритическая возможность засесть на пару недель дебага не достаточно?
На x86 cейчас там обычный cmp с ячейкой, реально генерируемый код выглядит вот так:
00007ff9`6153096b 80790900        cmp     byte ptr [rcx+9],0
00007ff9`6153096f 7549            jne     00007ff9`615309ba


Можешь теоретически объяснить, почему так? Почему не
mov rax, byte ptr [rcx+9]
cmp rax,0
jne SomeOffset

???
Полагаю, что потому что в системе команд присутствует cmp с ячейкой памяти? Ну ещё это может быть быстрее.
А если нет, если через регистр окажется быстрее!?


vlp>На практике и без volatile этого не делается, вместо этого значение всегда читается из памяти (почему-объяснять относительно долго)/


Даже на практике в диассемлерных листнингах встречается вот такое:
00007ff9`615508fb 488b4808        mov     rcx,qword ptr [rax+8]
00007ff9`615508ff 83790c02        cmp     dword ptr [rcx+0Ch],2


Т.е. в этом случае действуют через промежуточный регистр. Почему ты думаешь, что в случае ThrowIfDisposed() не может произойти тоже самое?
Всё сказанное выше — личное мнение, если не указано обратное.
Re[9]: Многопоточность
От: vlp  
Дата: 22.12.20 20:01
Оценка:
Здравствуйте, Философ, Вы писали:

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


Ф>>>Примеры дизассемблера тебе ничего не дадут: сегодня это один пример, а завтра версия джиттера поменяется.

vlp>>Я просил пример того, как прямо сейчас, с текущей версией компилятора и джиттера, возможна ситуация, которой тут всех пугают и как volatile в ней помогает.
vlp>>Тут, судя по ответам, в треде одни философы-теоретики, примеров нет.

Ф>Зачем тебе реальный пример? Т

ну как зачем — чтобы спровоцировать кого-то подумать

>Тебя теоритическая возможность засесть на пару недель дебага не достаточно?

неа

Ф>Даже на практике в диассемлерных листнингах встречается вот такое:

Ф>
Ф>00007ff9`615508fb 488b4808        mov     rcx,qword ptr [rax+8]
Ф>00007ff9`615508ff 83790c02        cmp     dword ptr [rcx+0Ch],2
Ф>


Ф>Т.е. в этом случае действуют через промежуточный регистр.

в этом случае сначала из памяти читается адрес, а потом опять из памяти сравнивается значние по другому адресу. Это чтение поля класса, когда где-то есть указатель на инстанс (он грузится в ecx, а сам лежит в [rax+8]). Какое отношение это имеет к поведению volatile?

> Почему ты думаешь, что в случае ThrowIfDisposed() не может произойти тоже самое?

произойти что именно? Покажите пример того, что именно произойдет и от чего вас убережет volatile.
Re[6]: Многопоточность
От: Философ Ад http://vk.com/id10256428
Дата: 22.12.20 20:25
Оценка:
Здравствуйте, vlp, Вы писали:

S>>сама же ms рекомендует этой переменной добавлять модификатор volatile.

vlp>Ну так не надо слепо следовать всем рекомендациям, не понимая их сути. В ряде контекстов эта рекомендация не имеет смысла, а иногда даже вредит.

Ну так я, например, реально натыкался на примеры того, что софтина зависает из-за кэширования значений в регистре. Т.е. там был выход из цикла по условию, а условие закэшировалось в регистре и реального чтения не происходило, т.е. вместо

mov r6, [rcx + myVarOffset]
cmp r7, [rcx + myVar2Offset]
jne ..

осталось только две последних строчки, и чтения в регистр не происходило. Запрет на оптимизацию (volatile) решило эту проблему. И было реально тяжело догадаться, что там вот такая засада. У меня там пара седых волос прибавилась. С тех пор и пишу volatile в таких местах — следую рекомендации ms. А учитывая, что бывают вещи и похитрее, я стараюсь исключать вообще даже вероятность таких ошибок, иначе на всю жизнь останешься в дебаге...
Всё сказанное выше — личное мнение, если не указано обратное.
Re[7]: Многопоточность
От: vlp  
Дата: 22.12.20 20:44
Оценка:
Здравствуйте, Философ, Вы писали:


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


Ф>mov r6, [rcx + myVarOffset]

Ф>cmp r7, [rcx + myVar2Offset]
Ф>jne ..

Ф>осталось только две последних строчки, и чтения в регистр не происходило. Запрет на оптимизацию (volatile) решило эту проблему.

я знаю, что происходит в случае, когда есть цикл и сохранение значния в регистре. Я просил пример именно с кодом из socket.cs. Там проверка ровно один раз в начале каждого метода. Циклов нет.
Re[6]: Многопоточность
От: Sharov Россия  
Дата: 22.12.20 21:40
Оценка:
Здравствуйте, vlp, Вы писали:

vlp>Ну так не надо слепо следовать всем рекомендациям, не понимая их сути. В ряде контекстов эта рекомендация не имеет смысла, а иногда даже вредит.


Я бы не сказал, что не понимаю сути-- запретить компилятору любые оптимизации с данным полем, чтобы где-то чего-то не переставить, закэшировать и тп. Просто этот конкретный случай хак ms -- не использовать volatile там, где в оф. документах и книжках использовать советуют.
Кодом людям нужно помогать!
Re: Многопоточность
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.12.20 02:20
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Код Socket.cs имеет автомарную запись в Dispose:

_NN> if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 1)

Это не атомарная запись. А атомарный обмен. Бул всегда атомарно читается и пишется.

_NN>Гарантируется ли в C# работоспособность этого кода ?

_NN>Не нужен ли где-нибудь volatile, Volatile.Read или Interlocked.Read ?

В общем, случае — нет. Но в CompareExchange происходит мемори борьер и если читать после него в том же потоке, то все будет ОК. Дучше добавить к _disposed volatile. Только это не про атомарность, а про сброс кэшей процессоров.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.