среда, 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 здесь. Вдруг вам будет интересно :).