среда, 5 мая 2010 г.

Coloroid: Silverlight Profiling by Example

Привет, друзья!

Предлагаю вам игру: Coloroid. Суть проста: у вас есть летающий мячик, отбивающий блоки. Выпустите мячик за пределы сцены - и вы проиграли. Звучит знакомо? Что же, уточнения. Размер летающего мячика - один пиксел. Размер блока - один пиксел. Если вам удается поймать отбитый осколок - он так же превращается в мячик.

Coloroid Start

Через некоторое время у вас в арсенале несколько тысяч мячиков, а число оставшейся работы уменьшается с завидной скоростью:

lookma

В игре есть 100 000 пикселей, которые предстоит разбить. В худшие времена, когда картинка почти вся разобрана по пикселям, а по сцене носятся десятки тысяч мячиков, на моем двухгигагерцевом пеньке FPS не опускается ниже 20. Как же достичь такой скорости?

Профилирование Silverlight приложений



Профилировать Silverlight приложения можно стандартными средствами Visual Studio 2010. Проблема лишь в том, что эти стандартные средства не имеют стандартных кнопочек, чтобы их запустить из IDE. Вместо этого - вооружимся консолью Visual Studio:

LaunchTools


Последовательность действий такова. Запускаем консоль VS 2010 (от имени администратора под Vista и выше), и выполняем команды:
cd {папка, где находятся скомпилированные Silverlihgt,  (не Web!) исходники}
VSPerfClrEnv /sampleon
"%ProgramFiles%\Internet Explorer\iexplore.exe" {Полный путь к тестoвой страничке SL приложения}
VSPerfCmd /start:sample /output:{Имя Файла с результатами профилирования} /attach:{PID процесса iexplore.exe}

{Играемся с приложением}

VSPerfCmd /detach
VSPerfCmd /shutdown
VSPerfClrEnv /off
Все, что находится в фигурных скобках, вы заполняете сами и без скобок. В моем случае эта последовательность выглядела так:

cd c:\Projects\Coloroid\Coloroid\Bin\Debug
VSPerfClrEnv /sampleon
"%ProgramFiles%\Internet Explorer\iexplore.exe" C:\Projects\Coloroid\Coloroid\Bin\Debug\TestPage.html
VSPerfCmd /start:sample /output:ProfilingName /attach:8816

Играемся с приложением

VSPerfCmd /detach
VSPerfCmd /shutdown
VSPerfClrEnv /off
Тройка важных моментов.

Момент 1. Запускать профайлер нужно именно из той папки, где находятся скомпилированные файлы сильверлайта. Иначе вы не увидите имена функций в файле профайлера:

Launch foloder is important

Момент 2.  Если вы запускаете приложение под IE8+, то вы должны указать идентификатор именно того процесса, в котором запущен Silverlight. Иначе вы будете профилировать код процесса-контейнера. Определить, какой именно процесс выполняет Silverlight - легко. Откройте диспетчер задач и поработайте со своим приложением. Кто загрузит процессор - тот и будет нашим кандидатом.

Момент 3. Как определить идентификатор процесса? - В диспетчере задач зайдите в  Вид-> Выбрать столбцы...


Pid

Если все сделано верно, то на выходе мы получим .vsp файл, который по умолчанию не ассоциирован с 2010-й студией, но легко открывается в ней. Вот, что получилось у меня:

Don't use DP's when do intensive calculations

Сюрприз! Функция, проверяющая столкнулся ли мячик с доской внизу, занимала львиную долю вычислений. В чем же дело?  Дело в том, что мы часто обращаемся к Dependency Properties прямоугольника (Width и Height). Это самая распространенная причина медлительности Silverlight/WPF приложений: использование dependency properties в интенсивных вычислениях. Решается простым кешированием значений перед выполнением алгоритма:
/* Где-то в классе Bar */
/* .. */

/// <summary>
/// Creates a new instance of <see cref="Bar"/>.
/// </summary>
/// <param name="barVisual">Visual presentation of the bar.</param>
public Bar(Rectangle barVisual)
{
    _barVisual = barVisual;
    _barVisual.Width = GameSettings.BarWidth;
    _barWidth = GameSettings.BarWidth;
    _barHeight = _barVisual.Height;

    _barTop = GameSettings.SceneHeight - 50;
    Canvas.SetTop(_barVisual, _barTop);
}

/// <summary>
/// Checks whether point with given coordinates hits the bar.
/// </summary>
public bool Hit(int x, int y)
{
    // Using _barVisual.Width & _barVisual.Heihgt was a bottleneck.
    // since they are dependency properties...

    //return x > _barLeft && x < _barVisual.Width + _barLeft &&
    //       y >= _barTop && y < _barTop + _barVisual.Height;

    return x > _barLeft && x < _barLeft + _barWidth &&
            y >= _barTop && y < _barTop + _barHeight;
}

Такое простое изменение позволило увеличить скорость в два раза.

Спасибо!


Вдохновение к этой игре я нашел на сайте wonderfl.net - потрясающий сайт, где собирается много талантливых Flash-разработчиков. Мне нравится копировать оттуда flash работы. Не потому, что я тешу себя фразой Пикассо: "Хорошие художники копируют, великие - воруют". Нет. Просто таким образом ты не только сравниваешь две технологии, но и неизбежно учишь новое. Желаю вам, друзья, легкого профилирования, быстрых программ и неиссякаемого вдохновения!

PS: Исходники к игре я выложил под MS-PL здесь. Вдруг вам будет интересно :).

воскресенье, 25 апреля 2010 г.

Chess Url: Немного шахматных задач

Привет :)!

За неделю собралось немного красивых и сложных задачек (спасибо Андрею Котенко :)).

Задача 1


Белые начинают и дают мат в два хода.


Задача 2


Опять начинают белые и дают мат в опять два хода.


Задача 3


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

вторник, 20 апреля 2010 г.

Chess Url (Чешурл) - Easy Chess Puzzles Sharing

Привет, Друзья!

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

Как всегда, пошел в Google, в поисках подходящего сервиса, а там этих сервисов... что Кот наплакал. То, что удалось найти имело фантастически убогий интерфейс и потрясающее неудобство использования. Вобщем, почему бы не сделать свой сервис? - Сделал. О нем и речь в сегодняшней статье.

Знакомьтесь - Chess Url


Чешурл позволяет расставить фигуры на доске и получить URL к картинке, которая соответствует расстановке. Например, в следующем примере у белых есть прекрасный ход. Какой?

White to Move
Ход белых

Эта картинка имеет следующий URL:

http://chessurl.appspot.com/?RA8,PA7,KB4,PC5,PF6/RA6,PB5,PC6,PD5,KF7

До слеша идут белые фигуры (RA8,PA7,KB4,PC5,PF6), после слеша - черные (RA6,PB5,PC6,PD5,KF7). Фигура начинается с имени (K - King, Q - Queen, R - Rook (ладья), B - Bishop, N - Knight (конь), P - Pawn). Следующие два символа - координаты фигуры на доске.

Как делался Chess Url?


Программа состоит из двух частей: дизайнера и просмотрщика. Дизайнер написан на Silverlight 4.0, просмотрщик на... Python в Google App Engine. Суть дизайнера - дать пользователю интерактивность при создании шахматной комбинации. Просмотрщик отвечает за генерацию картинки по указанным параметрам строки запроса.

Исходники дизайнера (Silverlight части) можно скачать здесь. Я решил открыть их под Microsoft Public License. Что касается исходников просмотрщика - позвольте оставить их за кулисами... Дело в том, что мои познания в Python настолько глубоки, что написанный код стыдно показывать blush. Отсутствие Python-исходников попробую компенсировать, поделившись полученным опытом.

  • Откуда картинки? - Изображения шахматных фигур я нашел на Википедии, в формате SVG.

  • Как конвертировать SVG в XAML? - Нужно скачать последнюю версию Inkscape. Редактор позволяет экспортировать SVG в XAML, и является абсолютно бесплатным. Единственный минус, у меня некорректно обработалась одна трансформация в SVG файле. Пришлось дотачивать напильником, но процесс этот безболезненный, т.к. форматы очень похожи.

  • Как происходит отрисовка картинки по строке запроса? - В состав Google App Engine входит Images API, который позволяет производить трансформации над картинками. Однако тут не без сюрпризов. Когда объединяешь .PNG файлы с прозрачностью, App Engine игнорирует прозрачность, заливая все белым цветом (как на эмуляторе сервера, так и на сервере). Решилась проблема использованием .GIF файлов. Благо Paint.NET легко конвертирует файлы.

  • В чем разрабатывалось Python приложение? - Мне очень понравилась Aptana. Это IDE, с открытым исходным кодом. Для Python устанавливается плагин, и вы получаете хороший user experience, имея полноценный intellisense в Python. Кстати, этим же IDE я пользуюсь для создания JavaScript приложений (прости меня Студия, но ты и рядом не стояло).

  • Сколько времени ушло на разработку? - около 4-х часов. Но я молчу о поиске изображений и их конвертировании. Так же промолчу и о поисках багов и их "конвертировании" :). Суммарных усилий хватило на два выходных дня.

Самым приятным сюрпризом был App Engine. Во-первых, очень либеральные квоты (один гиг трафика на вход и один на выход... в день). Во-вторых, простота программирования. Я заметил, что много потребляю ресурсов на конвертирование изображений (а это тоже входит в квоту: 2 592 000 трансформаций в день). Решение в три строчки кода: прикрутил Memcache. Теперь число трансформаций упало в разы.

Что вы думаете?


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

Спасибо за ваше внимание! Отличного программирования :)!

понедельник, 8 марта 2010 г.

Pro Debugging: А вы умеете?

Привет, Друзья!

Сегодня мы отвлечемся от Silverlight'a и пройдемся по самому популярному инструменту .NET разработчика: по отладчику Visual Studio, в контексте языка C#. Прежде чем вы подумаете, что я окончательно спятил, если собираюсь рассказывать очевидные вещи, позвольте возразить. Мы, разработчики ПО, склонны останавливаться на выученном, после того, как думаем, что знаний достаточно для выполнения работы. В то же время, копнув глубже, можно найти более эффективные способоы решения проблемы. Думаю, это одно из отличительных свойств настоящих Специалистов - желание всегда копать глубже, чем большинство, не останавливаясь на достигнутом.  

Итак, приятного вам "копания". Да не испугает вас размер скроллбара - тут картинок больше, чем текста :).

Содержание


Для вашего удобства, вот список всех полезностей, рассмотренных в этом посте:

Наблюдение за экземплярами классов или Make Object ID


Когда вы работаете с несколькими экземплярами класса, как можно отличить один от другого? Если это неуправляемый код, можно посмотреть на адрес объекта. Если вы в управляемом коде, то можно попросить GetHashCode() у объекта, и надеятся, что его реализация не подкачает. В отладчике есть более простой способ: правый клик на объекте в окне Watch/Locals/Autos -> Make Object ID.

Context menu: Make Object Id

После этого объекту присваивается уникальная метка {1#}, {2#}, и т.д. Метка дается объекту на все время жизни, и именно так объект будет появляться во всех окошках, в том числе, в Immediate Window, и даже в тултипах.

1. Делаем Object ID:

Make object id

2. Объект получает идентификатор:

Got Id

3. Теперь всюду, где встречается "меченный" объект, студия даст нам знать об этом:

Id everywhere

Что еще более интересно, так это возможность использовать метку для ссылки на объект. Т.е. можно написать в окне Watch 1# и получить мгновенный доступ к содержимому меченного объекта. Также метку можно использовать в условных (conditional) точках останова. Например, условие this == 1# остановит выполнение программы только если это наш меченный объект.

Улучшаем читабельность в Watch window


По умолчанию, значение объекта в окне Watch - имя его типа. Хочешь больше - раскрывай дерево. Но ведь для списков мы видим число элементов в списке. Значит, должен быть способ повысить читабельность нашего класса Customer?

Default Watch Value

ToString() - подумаете вы, и будете практически правы. Можно перекрыть метод ToString() и выводить отладочную информацию, которая будет показана в Watch. Но ToString() не всегда является лучшим выбором. Например, WPF использует его для вывода элементов без определенного DataTemplate на UI. Более удобным методом будет использование атрибута DebuggerDisplay.

[System.Diagnostics.DebuggerDisplay("Customer: {Name}")]
public class Customer
{
  public string Name { get; set; }
  // ...
}        

Перекомпилируем приложение, и под отладчиком получаем:

I Can Read This

Когда определены и атрибут DebuggerDisplay и ToString(), отладчик использует атрибут. Однако будьте аккуратны, в некоторых случаях вы можете получить интересное насекомое, зарегистрированное под именем Гейзенбаг.


Поймать все исключения? - Легко!


Отладчик может останавливаться при возникновении любого исключения. Даже на обработанных исключениях. И на исключениях в недрах библиотек. И даже на обработанных исключениях в недрах библиотек. Особенно полезным это может быть, когда причина бага, который вы пытаетесь поймать, кроется в "придушенном" исключении:

try
{
  DoSomethingBuggy();
}
catch
{
  // Sorry. 
}
Все, что нужно для остановки, это зайти в меню Debug -> Exceptions... и выбрать  на чем именно вам интересно было бы остановиться:

Catch them all

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


Исключение, ты кто? - $exception.


В предыдущем примере, когда мы останавливались на всех исключениях, вы могли встретить код следующего типа:
try
{
  // Do something
} catch
{
  // Catch 
}
Как теперь быть, если вы хотите посмотреть на исключение в окне Watch? Экземпляр исключения не указан в блоке catch, что написать? Конечно, можно добавить переменную исключения, перекомпилировать код, и посмотреть на красавца. Но ни в коем случае не делайте так. Просто введите в окно Watch строку $exception:

No Name

PS: Какие еще псеводпеременные поддерживает отладчик? Для C# мне известна только $user, которая показывает текущего пользователя. Для неуправляемого кода их чуть-чуть больше.


Debugger.Break(), или Стоп машина!


Чтобы мгновенно провалиться в отладчик, даже когда программа запущена не в режиме отладке, и даже когда она запущена в Release версии, достаточно вызвать Debugger.Break().


Поток, тебя как зовут?


Когда создаете новый поток, не поленитесь дать ему имя (Thread.Name). Так будет легче отличать потоки друг от друга во время отладки: на скриншоте ниже Happy thread получил имя.

Thread Name


Позвольте к вам прикрепиться: Attach to Process.


Расставили все брякпоинты, прошли первые десять шагов багорепа, и вдруг вспомнили, что программа запущена не под отладчиком? Ничего страшного. Выбираете в меню Debug -> Attach to Proces или просто Alt + D, P.

Attach to process

После этого, выбираем процесс, который хотим отладить, и жмем Attach. Если все загруженные символы соответсвуют прикрепленному процессу, вы попадаете в мир полноценной отладки.


Отладка без отладки: Design Time Debugging.


Immediate Window (Ctrl + Alt + I, или Debug -> Windows -> Immediate) позволяет выполнить метод даже если программа не запущена. А если еще поставить точку останова в методе, то мы попадем в отладчик:

 Design Time Debugging

Для того, чтобы отладить функцию Sum() достаточно ввести в Immediate Window'e выражение ?Sum(x, y), и нажать Enter. Эта фича называется "Отладка в режиме дизайна", и работает во всех редакциях студии, кроме Express'a.


Хочу остановиться здесь, если только...


Не можете понять как эта переменная становится null'ом? Или как этот счетчик выходит за пределы? Установите условный breakpoint. Для этого нужно добавить точку останова и тыкунть правой кнопкой мыши по ней. В контекстном меню выбираем "Condition...":

Conditional Context

И указываем при каких условиях должна сработать точка останова:

Debug Conditional Set

В следующий раз, когда вы войдете в функцию, и x будет меньше сработает точка останова.

Совместно с метками объектов условные точки останова представляют отличное подспорье, когда вы пытаетесь отладить конкретный экземпляр коллекции. Если вы хотите, чтобы точка останова срабатывала только для данного элемента коллекции достаточно пометить элемент коллекции и использовать его метку в условии. Например условие 1# == this остановит выполнение только в случае если текущей объект обладает меткой 1#.


Вывод информации, без вывода информации: Trace Points.


Приходилось ли вам когда-нибудь писать следующий код для упрощения отладки?
private static int Sum(int x,  int y)  int y)
{
  Trace.WriteLine(string.Format("Enetring function Sum(). X = {0}; Y = {1}; Time = {2}", x, y, DateTime.Now));
  // ...
}
Да? - Поздравляю, вы делали ненужную работу. Я тоже так делал :). Ведь это очень полезно, особенно когда ищешь баги, связанные с фокусом ввода - переключение в отладчик влечет за собой потерю фокуса, и усложняет отладку. А так - написал и смотришь в Output Window, что там куда приходит. Но есть способ сделать это легче.

Устанавливаем breakpoint, кликаем по нему правой кнопкой, выбираем "When Hit..." и в появившемся окошке пишем, что бы мы хотели увидеть:

Trace points

Когда отладчик войдет в эту функцию, в окошке Output появится следующее сообщение:
Entering ConsoleApplication1.Program.Sum(int, int); X == 0; Y = 1; Time = {19.02.2010 8:30:34}
Как видите, можно выводить не только значения переменных, но и значения вызовов функций или выражений. Безусловный плюс - не нужно перекомпилировать приложение, если хотите изменить что-либо.

PS: Это не значит, что Trace.WriteLine() становится вовсе бесполезным. Он все так же бесценен для сопровождения приложений.


Когда очень плохо: Отладка .NET framework'a.


Я люблю копаться в framework'e. Эта страсть к археологии позволяет не только лучше понять работу Библиотеки но и понять почему вызов этого метода не привел к ожидаемым результатам. Для отладки  .NET Framework вам нужны символы, флажок и желание поискать на форумах, почему имея первые два пункта вы до сих пор не можете отлаживать исходники .NET'a.

В самом простом случае, достаточно установить флажок в настройках студии Enable .NET Framework source stepping (Tools -> Options -> Debugging -> General):

Enable .NET Source Code

Я также вЫключил флаг Requre source files to exactly match the original version. Это увеличивает шансы на успешный вход в исходники .NET, в случае если загруженные символы по какой-то прчине отличаются от загруженной библиотеки.

Далее соглашайтесь на все предложения студии. Запускайте программу в дебаге, и попробуйте войти внутрь вызова системного метода. Если в Output окне вы видите сообщение "Step into: Stepping over method without symbols '...'":

Step into failed

Значит нужно проверить, что символы загружены. Идем в Call Stack и смотрим на методы без символов (серые):

Load Symbols

Тыкаем правой кнопкой по любому из них и выбираем Load Symbols From -> Microsoft symbols server. После этого студия начинает скачивать символы:

Load symbols progress

Загрузить и проверить статус дополнительных символов можно так же из окна Modules (Debug -> Windows -> Modules). Итак, символы загружены, пробуем войти в функцию еще раз:

Hurray

Что самое приятное - можно прочитать комментарии разработчиков библиотеки, которые нельзя найти ни в одном Reflector'e и MSDN'e.

Напоследок хочу предупредить, что не для всех сборок .NET Framework Microsoft позволяет отлаживать исходники. Смотрите в Output Window.

Отладка запуска сервисов/приложений.


Иногда нужно отладить код запуска сервисов или приложений,  которые стартует другой процесс (например, installer). Нажать F5 для запуска сервиса вы не можете, а прикрепиться к процессу - уже слишком поздно. Как быть?

А быть надо с редактором реестра. Идем в раздел "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" и добавляем новый подраздел, с названием процесса, который будем  отлаживать (e.g. explorer.exe). Затем, в этом разделе создаем строковый параметр с ключом debugger и значением - указывающм на отладчик:

HP Health Monitor

На картинке выше я решил отладить сервис HP Health Check (не потому, что у моей хапешки проблемы со здоровьем, тьфу-тьфу-тьфу, просто ради примера). Имя сервиса hphc_service.exe - именно так я назвал подраздел в Image File Execution Options. VSJitDebugger - это процесс запускающий отладчик Visual Studio. Теперь, если кто-то запустит процесс мы получим окошко предлагающее отладку:

Debug it

Кстати 1. Этот подход помогает не только отладить приложения, но и выследить какой засранец (извините) запускает исполняемые файлы вирусов...

Кстати 2. Если вы когда-нибудь устанавливали Process Explorer от SysInternals, и заменяли им стандартный диспетчер задач, то теперь вы знаете как он подменяет запуск taskmgr.exe :).


Отладка Silverlight приложений.


Мой основной браузер - Google Chrome, но разработку я веду под Firefox. Как же отлаживать Silverlight в таком случае? Один способ - это в настройках веб проекта указать каким браузером вы хотите запустить сайт, но я предпочитаю просто прикрепляться к процессу Firefox.

В четвертом Silverlight появилась удобная возможность отлаживать Out of Browser приложения. Достаточно указать в настройках Silverlight проекта какое именно приложение нужно отладить:

OOB Silverlight Debugging


Знайте свой инструмент, или блог Сары Форд.


Sara Ford - Program Manager CodePlex, автор книги MS Visual Studio Tips, очень жизнерадостная и веселая женщина. Она ведет потрясающий блог, огромная часть советов из которого и составила книгу. Если вы думаете, что отлично знаете Visual Studio - посетите ее блог, чтобы понять, насколько глубоко заблуждаетесь :).

Спасибо!


Друзья, спасибо вам, что прочитали или просто пролистали до этого места. Надеюсь, вы нашли что-нибудь новое для себя. Буду очень рад, если вы поделитесь своими открытиями, которые не вошли в пост. Напоследок, хотелось бы упомянуть слова мудреца: If debugging is the process of removing bugs, then programming must be the process of putting them in... Отличного настроения и программирования :)!