понедельник, апреля 24, 2006

Ключевое слово volatile

В C++ есть ключевые слова, окруженные некой аурой загадочности. Просто потому, что используются они редко, в специфических случаях. volatile, mutable, typename, explicit... Несмотря на редкость использования все равно интересно знать что это такое. Да и вообще как-то неприятно, когда встречаешь и не знаешь что это.

volatile мне встречалось дважды. И оба раза мне не сразу удавалось понять, что же хотел сказать автор. При первом взгляде, это ключевое слово, скорее всего, вызовет какие-то смутные ассоциации: "мммм..... это вроде что-то связанное с потоками" (здесь речь о потоках, которые "thread", а не которые "stream"). На самом деле, указывая volatile при объявлении переменной, программист просит компилятор не оптимизировать эту переменную.

Компилятор скорее всего оптимизирует код вроде такого, если переменная cancel не менялась в теле цикла.


bool cancel = false;
while( !cancel ) {
;
}

Если cancel не меняется, то ее и проверять каждый раз незачем, компилятор и не будет ее проверять.

Зато если вы укажете перед переменной volatile, то оптимизиации не будет. Предполагается, что переменная cancel могла измениться каким-то волшебным образом.

volatile bool cancel = false;
while( !cancel ) {
;
}

Каким волшебным образом? Из другого потока? Но обеспечивать доступ к переменным из разных потоков с помощью volatile не есть хорошо. Потому что переменную надо лочить при считывании и записи. Чтобы не получилось, например, такой ситуации: один поток пишет в переменную, другой в этот же момент считывает и считает наполовину старое значение, наполовину новое, короче не пойми что получится. А если ее лочить, то и volatile не нужен. Доступ к переменным может лочиться по-разному, но по тому, что я читала в форумах, где бы вы ни лочили, volatile в дополнение к локу указывать не нужно. Более того, использование volatile в данной ситуации может сказаться на производительности.(Updated 09.01.2009: это не так как минимум для gcc 3.3.6)

Чисто теоретически указания volatile при работе с потоками достаточно, если тип данных, с которым идет работа, может быть записан на данной архитектуре атомарно, в один прием. Соответственно, надо точно знать к каким именно типам это относится. Казалось бы уж что-что, так bool должен писаться в один прием. Я вычитала, что на некоторых Windows'ах это вовсе даже и не так. И атомарность присутствует только при работе с char...

Несмотря ни на что, volatile таки используется для доступа к переменной из разных потоков. Глобальная переменная объявляется volatile и вперед. И дважды я встретилась именно с таким использованием volatile. Но, как уже сказано выше, это не корректно.

Update 3.11.2006
volatile в исполнении Микрософт имеет Microsoft Specific пункт. А именно: атомарность операций гарантируется и, как следствие, использование в многопоточных программах приветствуется. Но код получается непортируемым, соответственно.
Я нашла этот пункт только в документации к Visual Studio 2005. Я порылась на MSDN в поисках этого пункта в других версиях Visual Studio, не нашла.

А зачем вообще тогда нужно ключевое слово volatile? Джеймс Канзе пишет, что задумывался volatile для работы с memory mapped IO (MMIO). Интересно он пишет, переведу кусок подробно

На уровне железа многие процессоры просто резервируют блок адресов памяти для портов ввода-вывода. Большинство процессоров имеют отдельное пространство адресов ввода-вывода, со специальными инструкциями для доступа туда, но это не универсально (на PDP-11 такого не было, например) и даже сейчас, производители железа могут предпочесть использовать для этого адресное пространство памяти, по разным причинам. Я сомневаюсь, что кто-то так делает на архитектуре 8086 - различные адресные ограничения делают это очень сложным. Я видел это на 8080, это очень частое решение на старой TI 9900. И это был единственный способ организовать ввод-вывод на наборе инструкций PDP-11, там просто не было отдельного адресного пространства ввода-вывода (Я думаю, то же самое верно и для VAX. И не забывайте, что большинство работы на С раньше проходило именно на этих двух процессорах).

Теперь рассмотрим один из первых последовательных портов, что я использовал: Intel 8051. Нормальным способом его инициализации было записать 0 три раза в порт управления. Если у вас MMIO, то код на С мог бы выглядеть примерно так:

unsigned char* pControl = 0xff24 ;
*pControl = 0 ;
*pControl = 0 ;
*pControl = 0 ;

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


Еще пример из того же обсуждения.

char getChar()
{
static unsigned char const charAvail = 0x01 ;
volatile unsigned char* pStatus = (unsigned char*)0x1234 ;
volatile char* pData = (char*)0x1230 ;

while ( (*pStatus & charAvail) == 0 ) {
}
return *pData ;
}

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

Это все не очень актуально для Windows, Unix систем, там есть свои способы работы с MMIO.

Еще volatile, вернее выражение вида volatile sig_atomic_t, может понадобиться для работы с сигналами. По сигналам я откопала большой FAQ на русском языке, про volatile sig_atomic_t там есть.

volatile используется с setjmp/longjmp, для восстановления значения переменных. Это тоже весьма редко используемые функции, и, судя по тому, что я о них читала, компиляторы почти всегда при их использовании все сами могут восстанавливать. Если вы их используете или встретили старый код с ними, то вот, зачем тут может быть volatile: Volatile type or qualifier?.

Кроме volatile переменных бывают еще и volatile функции и volatile классы. Я знаю лишь один случай их применения. Есть весьма известная и очень критикуемая статья Андрея Александреску volatile - Multithreaded Programmer's Best Friend, где он, используя volatile, смог добиться безопасной работы с потоками с проверкой на этапе компиляции.
Он использует только синтаксические свойства volatile. Связь volatile и оптимизации никак не используется. С тем же успехом он мог бы использовать и const. Но const гораздо чаще применяется по своему прямому назначению, чем volatile, поэтому использование const создало бы больше неудобств.

В начале следующей статьи Андрей суммирует критику Generic: Min and Max Redivivus. По поводу чего критика... Во-первых, он начинает статью с того, что рассказывает о том, как хорошо volatile применять при многопоточном программировании. Что, как было сказано выше, вообще говоря неверно. Потом он снимает volatile с помощью const_cast с переменной, которая реально является volatile. Несмотря на то, что скорее всего ничем плохим это не обернется, по Стандарту C++ эта ситуация приводит к undefined behavior. Но все это ничуть не умаляет значимость этой гениальной задумки. Насколько ее удобно использовать в реальном коде это вопрос, но хотя бы с теоретической точки зрения она представляет большой интерес.

Вот и все, что мне удалось найти о volatile. Пост получился не о том как лучше и удобнее применять volatile, а скорее о том, что думать, когда вы встретите volatile в чужом коде. А вообще можно всю жизнь программировать на C++, но с volatile так и не встретиться.

Ссылки по теме:
Подробные и наглядные примеры работы компилятора при указании volatile с диассемблированными примерами кода: раз и два.
comp.lang.c++.moderated: Q: infos, articles, faqs about 'volatile'
comp.lang.c++.moderated: volatile, was: memory visibility between threads - очень большое обсуждение статей Александреску.
comp.lang.c++.moderated: volatile -- what does it mean in relation to member functions?
Doug Harrison Microsoft MVP - Visual C++ об использовании volatile
comp.lang.c++.moderated: Why use volatile anyway?
comp.programming.threads FAQ

среда, апреля 19, 2006

Уилл Райт об игре Spore

Наткнулась тут на старую статью на GameSpy о выступлении Уилла Райта (Will Wright) на GDC2005. Это тот, кто сделал SimCity и The Sims. Он рассказывал о своей новой игре Spore и, в частности, говорил об анимировании персонажей. Да, поскольку это не само выступление, а рассказ о нем, подозреваю, что автор статьи мог чего-то где-то и напутать.
Итак, об анимировании. Райта раздражало, что в The Sims у персонажа было около 22000 различных анимаций. Он заинтересовался тем, что делают демосценеры, где персонажи, текстуры, весь контент, генерится процедурно. Он нанял демосценеров для программирования Spore, в итоге в игре контент генерится автомагически. Во время демонстрации игры он себе генерил чудовищ различных конфигураций и они сами разбирались как им правильно двигаться. Например, чудовище о трех ногах само разобралось как ему на этих трех ногах идти.
В статье речь идет не только об анимации, там много рассказывается об игре Spore, хотя технических подробностей маловато.


Ссылки по теме:
.kkrieger
Раздел на Wikipedia, посвященный игре Spore

понедельник, апреля 17, 2006

О работе Present

На блоге My own little DirectX FAQ опубликован пост Why is Present so slow?, в котором рассказывается, что именно делает функция Present и почему она может притормаживать.

понедельник, апреля 10, 2006

The Pragmatic Programmer Quick Reference Guide

На блоге Coding Horror собраны советы программисту из прекрасной книги The Pragmatic Programmer: From Journeyman to Master. Советы вроде "Provide Options, Don't Make Lame Excuses" и "Always Use Source Code Control". В книге каждый из них объясняется подробно, а здесь они собраны вместе в список: The Pragmatic Programmer Quick Reference Guide.

вторник, апреля 04, 2006

Частичный перевод обсуждения Microsoft's Not So Happy Family на slashdot.org

Это в дополнение к предыдущему посту "Обсуждение Windows Vista на блоге Mini-Microsoft".

Первое апреля прошло, со slashdot убрали розовенькую темку с сердечками, его вновь можно читать без отвращения.

slashdot конечно не мог остаться в стороне от недовольств, высказанных на Mini-Microsoft. Вообще много кто об этом пишет...

Сообщение от 26 марта на slashdot.org: Microsoft's Not So Happy Family

"Новости из Редмонда: сотрудники Микрософт недовольны задержкой Windows и Office до 2007 года. EETimes сообщает, что некоторые сотрудники Микрософт требуют уволить некоторых топ-менеджеров, включая Браена Валентина (Brian Valentine), Джима Олчина (Jim Allchin) и Стива Балмера (Steve Ballmer). Это сообщение ссылается на блог анонимного сотрудника Микрософт по имени Who da'Punk, который спрашивает, кто же виновен в этой ошибке? На данный момент этот пост в блоге собрал около 350 комментариев как от людей из Микрософт, так и от людей вне его".


От себя добавлю, что там недобрым словом помянуется еще некто Синофски (Steven Sinofsky), ну а больше всего склоняют Балмера.

На slashdot'е народ тоже про Балмера не забыл, много издевок в его адрес. Мол, если бы можно было устранять баги, кидая в них стулья, то Виста давно была бы уже готова. Это ему вспоминают случай, когда Балмер узнал, что один из его сотрудников уходит в Гугл и начал материться и ломать мебель.

Также говорят, что Балмер пообещал "fucking kill" всех анонимных сотрудников с блога Микрософт, что "Анонимности не место в Микрософте", но оригинала этого заявления я не нашла, так что вот, только слух.

Просматривать комментарии на slashdot мне было проще, потому что там комментариям выставляются оценки, по тому насколько они полезны и информативны. То есть мусор весь отсеяли за меня.

Комментарий от человека, который уволился из Микрософта в конце 2005
"Очень много от среднего времени разработчика занимала работа над тем, чтобы удовлетворить людей, наблюдающих за процессом. И это мало содействовало, если вообще содействовало, общей стабильности кода.".

"Старшие менеджеры в Мирософт получают уже слишком хорошо, 500000 долларов год. И их слишком много, и они не просто не пишут код или не участвуют как-нибудь в создании продукта, так они еще и усложняют жизнь другим своими идеями по организации процесса и бюрократией."


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


комментарий пользователя dioscaido:
"На самом деле при разработке Висты было решено строго мониторить независимость каждых отдельных dll/exe. Они создавали послойную схему, где ни один компонент из слоя X не может зависеть от компонента на слое Y, при этом Y>X. Как цель, они хотели бы иметь возможность однажды провести линии между слоями и рассматривать их как автономные части, которые могут управляться независимо. То есть, если ты хочешь сделать билд Висты без интерфейса пользователя (гипотетически), ты можешь все урезать сверху, включая уровень пользовательского интерфейса, и не иметь проблем со всеми этими утилитами командной строки, которые думают, что они работают под шеллом, базирующимся на пользовательском интерфейсе.

Это все еще монолитная система, но есть интересное движение в сторону модуляризации".


drsmithy излагает эволюцию кода Висты, но откуда у него эта информация, он не упомянул.
"На самом деле Виста (NT 6.0) задерживается, потому что были "потеряны" 2 года работы между XP (NT 5.1) и Windows 2003 (NT 5.2). Возможно, Микрософту следовало бы выпустить "XP second edition" (NT 5.3) [...]

Схематично, вот как выглядит дерево последних версий NT.



Сначала, XP разделяется на Windows 2003 и Висту (Longhorn). Но примерно в то время, когда вышла Windows 2003, они решили, что это гораздо лучший базовый код для разработки Висты, так что существующий код Висты был выброшен и проект начался по новой от Windows 2003 (вернее, многое в разработке Висты и Windows 2003 было одинаково и, технически, сохранено).

Интересно заметить, что FreeBSD имела схожие проблемы в базах кода 4.x, 5.x и 6.x. [...]

Вся суть в том, что разработка софта - это не та область, в которой прогресс работ хоть где-то близок к эскпоненте. Если что-то от экспоненты и есть, так это прямая ее противоположность. Чем дольше идет разработка, тем она идет все медленнее. Кривая разработки софта больше похожа на колокол, чем на что-то линейное или на экспоненту."


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

Прошлись и по поводу совместимости с приложениями. Микрософт придерживается той политики, что подавляющее большинство приложений, которые работали на старых версиях Windows, должны работать и на новой. Целые отделы работают проверяя приложения на совместимость. И если какое-нибудь приложение завязалось на заведомо неправильное поведение Windows, на баг, возникают проблемы. Потому что получается, что этот баг починить нельзя. В комментариях говорится, что в случае Linux'а баги фиксятся все равно, а работа такого приложения - это проблема разработчика этого приложения. А Микрософт со своим подходом получает целый букет проблем. У Реймонда Чена на блоге есть раздел History где он рассказывает о некоторых на первый взгляд неочевидных вещах, которые есть в Windows. В том числе много пишет и о совместимости с приложениями. Вот некоторые его посты
Why not just block the apps that rely on undocumented behavior?
Why are there two copies of Notepad?
Why does DS_SHELLFONT = DS_FIXEDSYS | DS_SETFONT?
The importance of error code backwards compatibility