Стеклянная луковица

Обо мне

Подписаться!Ленты RSS

Последние посты

 
 
Показаны сообщения с ярлыком development. Показать все сообщения
среда, декабря 02, 2009

Игры, в которые играют окна, и окна, которые играют в игры

2 комментариев

Собираясь отправиться в места не столь отдаленные, но лишенные прелестей всемирной сети, я прихватил с собой ноутбук, предварительно снабдив его рядом невероятно модных во времена своей беззаботной молодости игрушек. В число избранных попали такие шедевры, как Fallout 2, Baldur’s Gate, и более поздняя Age of Wonders 2. Проверив каждую из них на работоспособность в условиях свежеустановленной на лэптоп операционной системы Windows 7, я благополучно успокоился, будучи уверенным в том, что они помогут мне скоротать предстоящие долгие зимние вечера.

Каково же было мое удивление, когда, располагаясь уже на месте своего назначения, я решил все-таки вкусить немного постапокалиптики и, дважды кликнув по значку, изображающему Васька Трубачева, получил шиш с маслом! При этом я твердо помнил, что дома лично запускал все установленные игры и даже начинал играть в каждую из них. А в этот раз все, что я получил – это песочные часики на несколько секунд, и под завязку загруженный процессор. Взглянув в диспетчер задач, я заметил, что жадным до процессорного времени процессом оказался rundll32.exe, запустившийся вместе с fallout’ом. Завершив его, я немного побаловался с параметрами совместимости, но в каждом случае получал один и тот же результат. Смирившись с тем, что с киберпанком у меня не сложилось, я решил продолжить карьеру профессионального истребителя орков и прочих гоблинов, но к своему ужасу увидел, что с Baldur’s Gate и Age of Wonders 2 приключилась та же неприятность!

Интернета не было, выхода тоже, поэтому пришлось думать. Отбросив варианты саботажа, повреждения при перевозке, близкого расположения телевышки (облучающей меня и моего наколенного друга пропитанными свиным гриппом и предвыборной кампанией волнами) и проч, я остановился на наиболее вероятном – проявлении всемирного заговора. Чтобы подтвердить эту теорию, я с помощью наглядной студии приаттачился к гнусному процессу rundll32.exe, с надеждой если уж не поиграть, то произвести intercourse со своим мозгом. Предусмотрительно привязав себя к стулу, дабы не убежать в страхе из-за увиденного, я нажал кнопку Pause, вследствие чего заполучил на экране вываленную безо всякого сожаления тонну отдизасемблированного кода. Поставив брекпойнт в точке, на которой я приостановил выполнение программы, я на несколько секунд заснул на клавише F5, и по миганию красного кружка понял, что имею дело с зацикливанием. Чего, собственно, и следовало ожидать. Заглянув в Call Stack, я в который раз удивился, обнаружив там вызовы из wininet.dll. А после того, как методом последовательно восходящего брейкпойнтирования я локализовал место зацикливания, то оказался в gameux.dll.

Собственно, кусок кода, из-за которого произошло зацикливание:


Условный джамп в забрейкпойнченной точке всегда обходил стороной mov в следующей строке, и так происходило вечно... Все, что от меня требовалось в данном случае – это перетащить желтую стрелочку на строчку вниз, нажать F5, и вуаля – я в Fallout’е. Точно такой же трюк прошел и с двумя оставшимися игрушками.

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

P.S. А вот и описание аналогичной проблемы на форумах Microsoft - она, оказывается, тянется еще с билда 7100.

суббота, мая 23, 2009

Back to Basics

1 комментариев

«Вращающийся слэш» - едва ли не наиболее распространенный способ показать неопределенность прогресса в текстовом режиме. И, наверное, самый простой пример анимации. Вспоминаю полные штаны радости NN лет назад, когда на каком-то трипольском бейсике я воплотил этот неслабый прием в жизнь. И вот сейчас, в 2009 году, в эпоху 3D-ускоренных оконных элементов управления, объемных рабочих столов и градиентно-переливающихся прогресс-баров, не могу сдержать слез умиления на глазах, замечая в Microsoft-овских (графических!) инсталляторах такую вот крутяшку:

10

вторник, мая 05, 2009

Почему и как вредны маленькие числа

0 комментариев

Гегелевские законы диалектики применимы, пожалуй, для всех сфер человеческой деятельности, и вычислительная техника – тому не только не исключение, но и один из наиболее ярких примеров. На любом уровне, от электронных схем и до программных реализаций, разработчики пытаются совладать с парой взаимно отрицающих параметров вычислительных систем – точностью и быстродействием. Одна из наиболее интересных и животрепещущих историй этого связана с представлением вещественных чисел.

Как известно, стандарт IEEE 754 декларирует представление вещественного числа в виде числа с плавающей точкой. Такая форма основана на экспоненциальной записи вещественных чисел, которая в общем виде представляет собой нечто вроде:

где m – мантисса, n – основание системы исчисления, e – порядок.

Для однозначности предполагается, что мантисса нормализована – таким образом, что ее целая часть в терминах выбранной системы исчисления является цифрой, отличной от нуля.

Возвращаясь к стандарту, вспоминаем, что он для представления чисел с плавающей точкой одинарной точности отводит 32 бита. 1 бит здесь отводится на знак (s), 8 – на порядок (e), 23 – на мантиссу (m). Поскольку мы имеем дело, все-таки, с двоичной системой исчисления, то очевидно, что целая часть нормализованной мантиссы в нашем случае может быть равной только единице, посему она благополучно отбрасывается, и в выделенных 23-х битах хранится лишь дробная часть двоичного числа. Кроме того, для учета отрицательных степеней значение порядка считается смещенным на 127, т.е. фактическое его значение 0 соответствует степени −127, а 255 – степени 128. Со знаковым битом все просто: если он равен нулю, то знак – плюс, если единице – минус. Сведя все это воедино, можно записать наше число в таком виде:

Крайние порядки (0 и 255, т.е. степени −127 и 128) зарезервировали для специальных значений (бесконечностей, нечисел (NaN), и субнормальных чисел). Кроме того, поскольку точный нуль в таком виде представить не удастся, для него ввели частный случай, когда все биты мантиссы и порядка равны нулю. Тут случился конфуз, ибо с учетом знака стало возможным существование двух нулей: +0 и −0, но дело замяли, провозгласив их равенство (хотя если очень захотеть, то вывести нуль на чистую воду можно с помощью команды сопроцессора FXAM). Впрочем, приключения на этом не закончились, и связаны они были все с тем же злополучным нулем.

Наименьшее положительное число, которое может быть представлено в рамках наших ограничений (дробная часть равна нулю):

А следующее после него (в дробной части младший бит – единица):

То есть, расстояние между нулем и минимальным положительным числом составляет 2−126, а между минимальным положительным числом и следующим после него – 2−149! (Здесь нам открывается огромная черная дыра потери значимости (underflow) около нуля).

Для того чтобы заполнить этот пробел, был выдуман обходной маневр, который привел к появлению так называемых денормализованных (или, по свежему IEEE 754-2008, – субнормальных) чисел. Для их представления используется зарезервированная степень −127 (нулевое значение фактического порядка); если при этом мантисса ненулевая, то ее целая часть вместо единицы считается равной нулю, а показатель степени – равным −126. Денормализованное число тогда можно записать так:

Такой финт ушами (gradual undeflow) позволил избежать резкой потери точности в окрестностях нуля (flush to zero), хотя и вынудил производителей процессоров немного попотеть, что аукается до сих пор (к чему я, собственно, и веду). Известны случаи, когда обработка якобы тишины при ЦОС нагружает процессор сильнее, чем обработка обычного сигнала; а один и тот же код в графическом движке приводит при определенных условиях к провисанию быстродействия едва ли не на порядок (Van Verth, Bishop. Essential Mathematics for Games and Interactive Applications). Виной и тому, и другому – те самые маленькие денормализованные числа, любая работа с которыми, оказывается, производится большинством процессоров посредством специального микрокода - более медленного, чем стандартные инструкции математического сопроцессора. Чтобы убедиться в этом, достаточно набросать на коленке код, подобный приведенному ниже, и, запустив его (естественно, в режиме без оптимизации генерируемого кода), сравнить два выведенных числа.

#include <iostream>
#include <windows.h>

using namespace std;


float some( float val )
{
return val;
}


int main()
{
DWORD start = 0;
// обычное число с плавающей точкой
float f1 = 1.1f;
// денормализованное число
float f2 = 1e-40f;

start = GetTickCount();
for ( int i = 0; i < 10000000; i++ )
{
float f = some( f1 );
}
// время, затраченное на работу с обычным числом
cout << GetTickCount() - start << endl;

start = GetTickCount();
for ( int i = 0; i < 10000000; i++ )
{
float f = some( f2 );
}
// время, затраченное на работу с денормализованным числом
cout << GetTickCount() - start << endl;

cin.get();
}

Очевидный выход из сложившейся ситуации – в случаях, когда предлагаемая денормализованными числами точность не является необходимой, просто обнулять их, таким образом совершая программный flush to zero. При обработке звука, к примеру, этим отбрасывается фоновый шум низкой амплитуды, который, будучи неразличимым для слуха, просто загружает процессор. Ну а для графических движков подобная точность не нужна тем паче.

воскресенье, мая 25, 2008

.NET + Unmanaged Fortran

0 комментариев

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

В целом, задача моя состояла в следующем: взять код нескольких готовых функций на Фортране, несколько написать самому, скомпилировать получившееся в DLL, нежно обернуть библиотеку управляемым кодом, и вызвать из своей гуишки. Будем исходить из того, что получить необходимо 32-разрядное приложение.

Попытаюсь проследить весь процесс от начала и до конца :)

Писать/править код можно, в принципе, и в блокноте, а можно использовать неплохие бесплатные текстовые редакторы, подсвечивающие фортрановский синтаксис: к примеру, PSPad или Crimson Editor (первый мне понравился больше). Хотя, честно говоря, до UltraEdit’a им далековато. Он не бесплатен, и подсветка синтаксиса для Фортрана по умолчанию вместе с ним не идет – правила подсветки можно скачать с официального сайта программы. Но удобство редактора и набор возможностей позволяют закрыть глаза на эти мелочи.

Следующий шаг – выбор компилятора. Первым делом я ухватился за опенсорсовый вариант – G95. Мал, шустер и прост. Впрочем, на этом этапе у меня к компилятору было только одно требование – компилировать.

Итак, забрасываю все исходники в одну папку, решаю назвать свою библиотеку test.dll и вызываю команду:


g95 -s -shared -mrtd -o test.dll test.def *.f

Если верить FAQ’у, то здесь параметр -mrtd отвечает за соглашение передачи параметров STDCALL. В файле test.def разбираемся с декорированием имен: пусть экспортируем функцию testfcn, тогда, чтобы избавиться от знака подчеркивания при экспорте, пишем в нем:

EXPORTS testfcn=testfcn_

Итак, библиотека получена, теперь задача – обернуть ее в какой-нибудь класс на C#.

Пусть наша функция принимает параметрами xr, xi – вещественные массивы двойной точности, целые числа n и ord, и еще один вещественный массив y. Тогда метод, импортирующий функцию из нашей библиотеки, следует объявить следующим образом:

[DllImport( "test.dll", EntryPoint = "testfcn", SetLastError = true,
CharSet = CharSet.Ansi, ExactSpelling = true,
CallingConvention = CallingConvention.Stdcall )]
public static extern void testfcn( ref double xr,
ref double xi,
ref int n,
ref int ord,
ref double y );


При вызове функции, передавая массивы, даем ссылку на их первый элемент:

testfcn( ref xr[ 0 ], ref xi[ 0 ], ref n, ref o, ref y[ 0 ] );

Если работа выполняется на 64-разрядной машине, не забываем синхронизировать разрядность в настройках дотнетовского проекта.

В целом, на этом изыскания можно было бы и закончить, если бы не одно «но»: быстродействие полученного модуля меня абсолютно не устраивало. Поэтому мой взгляд упал на Intel Fortran Compiler, а именно – десятую его версию.

Он вполне неплохо проинтегрировался в Visual Studio 2005, поэтому нужда во внешних редакторах сразу же отпала. Порадовал широкий выбор опций, связанных с оптимизацией.

Вместо def-файла ему достаточно подсунуть такую директиву:

!DEC$ ATTRIBUTES DLLEXPORT, ALIAS: 'testfcn' :: testfcn

По советам Гугла пытался впихнуть туда же атрибут STDCALL, равно как и проставить соответствующую опцию в параметрах компилятора, но он упорно выдавал библиотеку с соглашением CDECL, поэтому C#-обвертку пришлось немного изменить, выставив для DLLImport’a CallingConvention = CallingConvention.Cdecl.

Еще одна проблема возникла с массивами – по умолчанию они передавались через стек, и даже при небольших объемах данных сыпались исключения переполнения стека. Лечится это добавлением ключика /heap-arrays в параметры командной строки компилятора.

Итак, выставив настройки оптимизации по своему вкусу (включая специфические для процессоров Intel), компилируюсь, запускаю программу на тест, и – получаю прирост производительности на 50% (!!!).

Единственный минус – при развертывании приложения придется тащить за собой целый ворох вспомогательных библиотек, от которых теперь, оказывается, зависит наш модуль. Их не так уж и много, они не такие уж и большие, но все равно вполне неприятно. При использовании G95 такой необходимости не было. Впрочем, в таких случаях я сразу вспоминаю псевдокомпилятор MATLAB’a со 150-мегабайтным установочником рантаймов, и на душе сразу становится легче :)