(no subject)
Nov. 10th, 2011 06:52 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Внезапно осознал, что совсем практически себе не представлял, что оптимизатор может делать, а что не может, работая с константной ссылкой (равно как и с указателем на константу) в С++.
Скажем, если мы будем в цикле локать мютекс из pthreads и проверять значение, на который указывает указатель на константу, далее отпуская мютекс и снова начиная тело цикла с начала -- есть ли у нас гарантия, что мы увидим изменение этого значения из-под другого треда, или же компилятор выпилит все проверки кроме первой?
Вообще-то говоря, такие вещи надо бы знать идеально. Это даже не таинственного мультитредного С++ касается, который вот уже полтора десятка лет живёт без всякой стандартизации, а и вполне обычного стандартизированного однотредного С++, где мы можем из-под выше описываемого цикла вызывать заодно и какую-то внешнюю функцию без аргументов, меняющую значение переменной, на которую у нас есть константый указатель. Надо бы мне это знать. А вот нет. Как-то проскочил мимо, или же знал, но давно позабыл.
Пока-что не нашел этого в стандарте описанным явно, но, судя по всяким косвенным признакам, гарантия такая (о том, что мы разглядим изменение переменной) имеется, и константность типа, на который указывает указатель, не является сама по себе основанием для каких-то дополнительных оптимизаций, которые не могут быть применены к указателю на неконстантный тип (здесь я коварно усмехаюсь).
"не является сама по себе основанием для каких-то дополнительных оптимизаций" -- если у кого-то есть возражения или комментарии по этому поводу, равно как и вообще, буду рад обсудить.
Скажем, если мы будем в цикле локать мютекс из pthreads и проверять значение, на который указывает указатель на константу, далее отпуская мютекс и снова начиная тело цикла с начала -- есть ли у нас гарантия, что мы увидим изменение этого значения из-под другого треда, или же компилятор выпилит все проверки кроме первой?
Вообще-то говоря, такие вещи надо бы знать идеально. Это даже не таинственного мультитредного С++ касается, который вот уже полтора десятка лет живёт без всякой стандартизации, а и вполне обычного стандартизированного однотредного С++, где мы можем из-под выше описываемого цикла вызывать заодно и какую-то внешнюю функцию без аргументов, меняющую значение переменной, на которую у нас есть константый указатель. Надо бы мне это знать. А вот нет. Как-то проскочил мимо, или же знал, но давно позабыл.
Пока-что не нашел этого в стандарте описанным явно, но, судя по всяким косвенным признакам, гарантия такая (о том, что мы разглядим изменение переменной) имеется, и константность типа, на который указывает указатель, не является сама по себе основанием для каких-то дополнительных оптимизаций, которые не могут быть применены к указателю на неконстантный тип (здесь я коварно усмехаюсь).
"не является сама по себе основанием для каких-то дополнительных оптимизаций" -- если у кого-то есть возражения или комментарии по этому поводу, равно как и вообще, буду рад обсудить.
no subject
Date: 2011-11-11 12:33 am (UTC)no subject
Date: 2011-11-11 03:26 am (UTC)Скажем, когда есть volatile ptr, компилятор полностью вырубает оптимизацию доступа, в случае же const ptr всё-таки некоторая оптимизация (которая может ударить по неправильно написанному мультитреду) присутствует.
Скорее, нужно говорить:
const never implies constness of pointed data.
Есть два вида семантики константности, отсюда и путанница.
no subject
Date: 2011-11-11 05:11 am (UTC)no subject
Date: 2011-11-11 05:20 am (UTC)no subject
Date: 2011-11-11 05:25 am (UTC)no subject
Date: 2011-11-11 10:51 am (UTC)вызовы недоступных оптимизатору фунций аналогичны memory fence. может статься, что это включает в себя и доступ к volatile переменным.
no subject
Date: 2011-11-11 03:50 pm (UTC)я не был уверен, верно ли это и для ссылок на константы.
* может статься, что это включает в себя и доступ к volatile переменным.
Нет, такое счастье - это proprietary implementation of MS VC++.
no subject
Date: 2011-11-11 06:11 pm (UTC)no subject
Date: 2011-11-11 06:29 pm (UTC)кстати sequence points из стандарта уже убрали, теперь там другие понятия..
no subject
Date: 2011-11-11 06:32 pm (UTC)да и в прошлом стандарте sequence points давно уже не были священной коровой, единственные гарантии вроде требовались лишь на observable behavior at sequence points, чем считается лишь volatile reads/writes и library IO (sic!)
no subject
Date: 2011-11-11 06:49 pm (UTC)речь о том, что они теперь в других терминах описывают модель вычислений - без sequence points
no subject
Date: 2011-11-11 06:29 pm (UTC)no subject
Date: 2011-11-11 06:33 pm (UTC)К volatile нифига всё не сводится.
В новом стандарте всё куда глубже расписано.
no subject
Date: 2011-11-11 06:35 pm (UTC)1.8, p5-6
no subject
Date: 2011-11-11 07:01 pm (UTC)Я чего-то немного запутался, всего месяц назад на эту тему что-то читал, но кажется читал что-то не то.
Есть два варианта - или мы будем обсуждать эту ссылку 97-го года, или можно попробовать подогнать цитаты из стандарта 2003-го года, и там уже разбираться что так, а что не так.
Вкратце и в общем, observable behavior это действительно модификации volatiles и i/o функции, но та или иная логика работы с внутренними переменными может повлиять на observable behavior.
Т.е. конечно формально можно вообще писать во внутренние переменные любой мусор, лишь бы observable был в порядке, но у нас как раз речь о модели внутренних вычислений, где как раз важно (в старом стандарте) понятие sequence points. Они задают не то, как на самом деле физически должны модифицироваться внутренние переменные, но как это должно выглядеть, какая формальная модель этого. Без понимания этого бесмысленно говорить и об observable behaviour.
no subject
Date: 2011-11-12 01:16 am (UTC)Приблизительно то же самое.
Так вот, поправь меня если я не прав, но чтение обычной, не volatile переменной (в отличие от её записи) - это никак не side-effect - согласно тому определению, на которое ты ссылаешься.
Но тогда компилятор вообще не обязан сериализовать относительно seqence point чтение памяти, на которую указывают указатели - будь они указателями на константу, будь они обычнми указателями.
Не правда ли, парадоксальный вывод? ;-)
no subject
Date: 2011-11-12 06:16 pm (UTC)в рамках внешнего контракта оптимизатор может творить что угодно, что не противоречит внутреннему.
получается, что разные треды могут полагаться лишь на внешний контракт, т.е. observable behavior, вроде так?
no subject
Date: 2011-11-12 07:02 pm (UTC)ок. приведу конкретный пример:
external int val;
void somefunc()
{
val=1;
}
void f1()
{
val = 0;
for(;;)
{
somefunc();
if(val)
break;
}
}
в соответствии с вышеизложенным спеком, функция f1 может никогда не окончиться. )))
* получается, что разные треды могут полагаться лишь на внешний контракт, т.е. observable behavior, вроде так
Стандарт 2003 года вообще никак не специфицирует работу с тредами.
Observable behaviour - это не то, что видят треды друг у друга, а то что видит "внешний наблюдатель", скажем, устройтсво в volatile регистры которого пишет программа или операционная система, функции ввода-вывода которой вызываются.
no subject
Date: 2011-11-13 10:04 am (UTC)это нарушение 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()
.no subject
Date: 2011-11-13 05:15 pm (UTC)> Accessing an object designated by a volatile lvalue (_basic.lval_), modifying an object
Ну и где там "modifying an object"? Я всего лишь читаю(!) неволатальную переменную в цикле -- и где же в стандарте написано, что эти чтения должны как-то сериализоваться? ;-)
> из этого как бы вытекает необходимость в расширениях типа asm volatile("" ::: "memory"); или _ReadWriteBarrier()
Разумеется, стандартный С++ был плохо приспособлен к мультитреду и нуждался в подобных расширениях. А что, ты полагал, что я как-то это вижу иначе?
no subject
Date: 2011-11-13 09:02 pm (UTC)хм, еще раз читаем:
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 не напишешь...
no subject
Date: 2011-11-13 10:38 pm (UTC)Разумеется :-)
чтение неволатальной переменной не есть сайд-эффект
вычисление выражения, если оно не сопровождается изменением объектов памяти не есть сайд-эффект
выход из оператора цикла не есть сайд-эффект :-)
наконец, я что-то пока не заметил, где написано, что "evaluation of an expression очевидно должно быть на основании этих самых свежих данных", но, как видишь, я и при наличии этого утверждения вполне вроде б аргументированно, показываю, что оператор цикла не закочится.
Кароче, кончай заниматься казуистикой. (Это я себе))) Стандарт 99&03 сдох, в новом стандарте всё чуток по другому, какой смысл заниматься толкованием этих текстов? - всё равно не ты не я не являемся специалистами по чтению подобного материала.
Хотя, вот если ты имеешь какие-то опровержения моих конкретных заявлений по С++ на основании стандарта, это было б небезынтересно рассмотреть.
Да, разумеется, и spinlock на нем не напишешь. Но заметь, что по факту это никого особо не колыхало и как в unix, так и в windows можно было писать вполне рабочие программы (учитывая определенное сотрудничество разработчиков библиотек синхронизации и разработчиков компиляторов). Ну да, совершенно вне стандарта, ну так и что?
no subject
Date: 2011-11-14 05:05 am (UTC)хочешь казуистики, гад? держи: функция обязана завершиться независимо от работы оптимизатора, иначе не завершится программа и не сможет вернуть exit code, а это observable behavior.
Не говоря уже о том, что нарушается та самая sequence of reads and writes to volatile data and calls to library I/O functions.
Но заметь, что по факту это никого особо не колыхало и как в unix, так и в windows можно было писать вполне рабочие программы (учитывая определенное сотрудничество разработчиков библиотек синхронизации и разработчиков компиляторов). Ну да, совершенно вне стандарта, ну так и что?
да блин, не в стандарте же проблема, и не в том, что можно даже, а в том, как поздно это осознается. столько всего лично нами написано - и не факт что оно "рабочее".
no subject
Date: 2011-11-14 02:57 pm (UTC)> Не говоря уже о том, что нарушается та самая sequence of reads and writes to volatile data and calls to library I/O functions.
Я устал. Может быть, через пару недель я к этому вопросу вернусь, но ей богу, в нашем разговоре хорошо б подключить какого-то эксперта по стандартам. Бо занимаемся непроизводительной казуистикой, решая не реальные задачи, а интерпретационные.
* столько всего лично нами написано
Говори плз за себя.
Вот мой пост от 2003-го года
http://ru-programming.livejournal.com/11316.html
А на ixbt меня за дискуссию по этой же теме просто забанили )))
Лично я, если ты помнишь, с полным осознанием того что делаю, ввел на атомарном типе атомарные записи и чтения с мамбарами. Другое дело, что если напустить на этот код глобальный оптимизатор, то он бы и это почикал (интересно, он умеет оптимизировать ассемблерные вставки?) но мы ведь не напускали ;-)
На самом деле, у нас наоборот была очень пессимистичная стратегия работы с атомикс, мы ставили мембары в куче мест, где без них можно было бы, возможно, и обойтись.
no subject
Date: 2011-11-14 03:59 pm (UTC)я даже знаю, благодаря кому :) вот вопрос и возникает - как корректно писать без тяжелых интерлоков.
офф - про keyed events (http://locklessinc.com/articles/keyed_events/) читал?
no subject
Date: 2011-11-14 04:22 pm (UTC)на это у меня есть два ответа - простой и сложный.
простой заключается в том, что с тех пор, как опубликован спек на модель доступа к памяти на i86 (вроде всего как 3-4 года назад), ты можешь юзать интерлоки только там, где они реально нужны. Скажем, посмотри atomic в С++ - он как раз дает "все возможные" мембары при доступе, начиная от совершенно "relaxed" вплоть до "sequentially consistent".
Я, между прочим, не думаю, что эти atomic написаны правильно в смысле перспектив их использования для написания идеального кроссплатформенного кода, но во всяком случае, при работе под конкретную платформу, ты можешь четко решать, какой уровень сериализации заказать при доступе.
Другое дело, что надо прокачать скиллзы, чтобы писать корректный код на минимально необходимой сериализации доступа к памяти. Я вот, например, не прокачал до сих пор, но понемножку над этим работаю.
А вообще, по-моему, не надо бояться тяжелых интерлоков. Всё равно мы не умеем писать хороший код под тяжелый мультитред, ну а под лёгкий, там где бегут 4-8 ядер, пожалуй, ничего страшного от пары-тройки лишних интерлоков не слуыится.
no subject
Date: 2011-11-14 04:25 pm (UTC)no subject
Date: 2011-11-14 04:27 pm (UTC)так я и говорю - это было моё решение, и я считаю его на тот момент и при том контингенте программистов - правильным ;-)
no subject
Date: 2011-11-15 03:18 am (UTC)no subject
Date: 2011-11-15 05:37 am (UTC)вот, нашлось:
http://www.bluebytesoftware.com/blog/PermaLink,guid,db9f8f5b-8d1d-44b0-afbd-3eadde24b678.aspx
no subject
Date: 2011-11-15 06:10 am (UTC)какой-то очень грязный патч, непонятно, как они могли подобное зарелизить в win2000
мы такого идиотизма не писали, если ты помнишь
no subject
Date: 2011-11-15 09:55 am (UTC)пишут же, что собрали статистику, уж наверное толк был.
ты наверное не понял - аллокация происходит in response to the first contended acquire, то есть захват свободной critical section до поры до времени происходит бездвоздмездно, то есть даром.
очень может быть, что "на тот момент и при том контингенте программистов" ;) это была ни фига себе оптимизация.
no subject
Date: 2011-11-15 01:17 pm (UTC)это ты, вродь, не понял, что со временем все critical sectionы зааллоцируют свои events. т.е. статистика тут может быть такая: "в среднем проблематичная программа навернётся через 15 суток интенсивной работы - офигительно долго, давайти это имплементируем!"
no subject
Date: 2011-11-15 03:22 pm (UTC)понимаешь, есть такое мнение, что lazy resource acquisition leads to better scalability. это как раз тот случай.