yigal_s: (Default)
[personal profile] yigal_s
Внезапно осознал, что совсем практически себе не представлял, что оптимизатор может делать, а что не может, работая с константной ссылкой (равно как и с указателем на константу) в С++.

Скажем, если мы будем в цикле локать мютекс из pthreads и проверять значение, на который указывает указатель на константу, далее отпуская мютекс и снова начиная тело цикла с начала -- есть ли у нас гарантия, что мы увидим изменение этого значения из-под другого треда, или же компилятор выпилит все проверки кроме первой?

Вообще-то говоря, такие вещи надо бы знать идеально. Это даже не таинственного мультитредного С++ касается, который вот уже полтора десятка лет живёт без всякой стандартизации, а и вполне обычного стандартизированного однотредного С++, где мы можем из-под выше описываемого цикла вызывать заодно и какую-то внешнюю функцию без аргументов, меняющую значение переменной, на которую у нас есть константый указатель. Надо бы мне это знать. А вот нет. Как-то проскочил мимо, или же знал, но давно позабыл.

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

"не является сама по себе основанием для каких-то дополнительных оптимизаций" -- если у кого-то есть возражения или комментарии по этому поводу, равно как и вообще, буду рад обсудить.

Date: 2011-11-11 12:33 am (UTC)
From: [identity profile] spamsink.livejournal.com
Другими словами, const does not imply non-volatile.

Date: 2011-11-11 05:11 am (UTC)
From: [identity profile] spamsink.livejournal.com
Какую оптимизацию компилятор имеет право делать для const ptr, которую он не сделал бы для обычного указателя?

Date: 2011-11-11 05:25 am (UTC)
From: [identity profile] spamsink.livejournal.com
Правильно. Поэтому const в данном случае - red herring. Если нет volatile, компилятор имеет право предполагать единственность управления.

Date: 2011-11-11 10:51 am (UTC)
From: [identity profile] mediant.livejournal.com
const это исключительно compile-time средство. оно не применяется оптимизатором для оценки поведения кода.

вызовы недоступных оптимизатору фунций аналогичны memory fence. может статься, что это включает в себя и доступ к volatile переменным.

Date: 2011-11-11 06:11 pm (UTC)
From: [identity profile] mediant.livejournal.com
есть ли в стандарте что-либо кроме sequence points, имеющее отношение к этой теме вообще? потому что для sequence points этот const-ness абсолютно пофиг.

Date: 2011-11-11 06:32 pm (UTC)
From: [identity profile] mediant.livejournal.com
c+11 atomics? я еще не читал...
да и в прошлом стандарте sequence points давно уже не были священной коровой, единственные гарантии вроде требовались лишь на observable behavior at sequence points, чем считается лишь volatile reads/writes и library IO (sic!)

Date: 2011-11-11 06:29 pm (UTC)
From: [identity profile] mediant.livejournal.com
собственно говоря, sequence points вроде уже даже не при чем, все вертится вокруг observable behavior, которое есть volatile reads/writes и вызов library IO (sic!).

Date: 2011-11-12 06:16 pm (UTC)
From: [identity profile] mediant.livejournal.com
не такой уж парадоксальный. внешний контракт это observable behavior, внутренний - side effects. и то, и другое применимо лишь к sequence points.
в рамках внешнего контракта оптимизатор может творить что угодно, что не противоречит внутреннему.

получается, что разные треды могут полагаться лишь на внешний контракт, т.е. observable behavior, вроде так?

Date: 2011-11-13 10:04 am (UTC)
From: [identity profile] mediant.livejournal.com
в соответствии с вышеизложенным спеком, функция f1 может никогда не окончиться. )))

это нарушение side effects.

Accessing an object designated by a volatile lvalue (_basic.lval_),
modifying an object, calling a library I/O function, or calling a
function that does any of those operations are all side effects, which
are changes in the state of the execution environment. Evaluation of
an expression might produce side effects. At certain specified points
in the execution sequence called sequence points, all side effects of
previous evaluations shall be complete and no side effects of subse-
quent evaluations shall have taken place

То есть программа сама *обязательно* увидит изменение переменной (кроме случаев типа a[i] = i++), а вот внешний наблюдатель (в том числе другой тред) - не факт.

Стандарт 2003 года вообще никак не специфицирует работу с тредами.
Observable behaviour - это не то, что видят треды друг у друга, а то что видит "внешний наблюдатель", скажем, устройтсво в volatile регистры которого пишет программа или операционная система, функции ввода-вывода которой вызываются.


в том то все и дело, что это единственные доступные для другого треда гарантии состояния памяти, иных просто нет в рамках языка.
из этого как бы вытекает необходимость в расширениях типа asm volatile("" ::: "memory"); или _ReadWriteBarrier().

Date: 2011-11-13 09:02 pm (UTC)
From: [identity profile] mediant.livejournal.com
и где же в стандарте написано, что эти чтения должны как-то сериализоваться?

хм, еще раз читаем:

Accessing an object designated by a volatile lvalue (_basic.lval_),
modifying an object, calling a library I/O function, or calling a
function that does any of those operations are all side effects, which
are changes in the state of the execution environment. Evaluation of
an expression might produce side effects. At certain specified points
in the execution sequence called sequence points, all side effects of
previous evaluations shall be complete and no side effects of subse-
quent evaluations shall have taken place


если side effects are понимаешь, complete, то evaluation of an expression очевидно должно быть на основании этих самых свежих данных. есть другое мнение?

Разумеется, стандартный С++ был плохо приспособлен к мультитреду и нуждался в подобных расширениях.

настолько не приспособлен, что любое обращение к shared data должно быть завернуто в "подобное расширение". на volatile даже spinlock не напишешь...

Date: 2011-11-14 05:05 am (UTC)
From: [identity profile] mediant.livejournal.com
в соответствии с вышеизложенным спеком, функция f1 может никогда не окончиться. )))

хочешь казуистики, гад? держи: функция обязана завершиться независимо от работы оптимизатора, иначе не завершится программа и не сможет вернуть exit code, а это observable behavior.

Не говоря уже о том, что нарушается та самая sequence of reads and writes to volatile data and calls to library I/O functions.

Но заметь, что по факту это никого особо не колыхало и как в unix, так и в windows можно было писать вполне рабочие программы (учитывая определенное сотрудничество разработчиков библиотек синхронизации и разработчиков компиляторов). Ну да, совершенно вне стандарта, ну так и что?

да блин, не в стандарте же проблема, и не в том, что можно даже, а в том, как поздно это осознается. столько всего лично нами написано - и не факт что оно "рабочее".

Date: 2011-11-14 03:59 pm (UTC)
From: [identity profile] mediant.livejournal.com
На самом деле, у нас наоборот была очень пессимистичная стратегия работы с атомикс, мы ставили мембары в куче мест, где без них можно было бы, возможно, и обойтись.

я даже знаю, благодаря кому :) вот вопрос и возникает - как корректно писать без тяжелых интерлоков.

офф - про keyed events (http://locklessinc.com/articles/keyed_events/) читал?

Date: 2011-11-15 05:37 am (UTC)
From: [identity profile] mediant.livejournal.com
очевидно на handles экономили, non-paged pool соответственно и всякое такое.

вот, нашлось:
http://www.bluebytesoftware.com/blog/PermaLink,guid,db9f8f5b-8d1d-44b0-afbd-3eadde24b678.aspx

Date: 2011-11-15 09:55 am (UTC)
From: [identity profile] mediant.livejournal.com
странное решение. программа всё равно со временем имеет шансы постепенно заллоцировать все events, переполнить таблицу хендлов и упасть.

пишут же, что собрали статистику, уж наверное толк был.

ты наверное не понял - аллокация происходит in response to the first contended acquire, то есть захват свободной critical section до поры до времени происходит бездвоздмездно, то есть даром.

очень может быть, что "на тот момент и при том контингенте программистов" ;) это была ни фига себе оптимизация.

Date: 2011-11-15 03:22 pm (UTC)
From: [identity profile] mediant.livejournal.com
тогда можно уж и память каждой программе преаллоцировать сразу гиг или два, все равно ведь может понадобиться?

понимаешь, есть такое мнение, что lazy resource acquisition leads to better scalability. это как раз тот случай.