Через тернии к звездам. Оптимизация кода ActionScript3.

Эта статья посвящена оптимизации ActionScript3 на примере создания эффекта «полета сквозь звезды»(на подобии старого скринсейвера Windows). Мне этот эффект необходим был для игры. Эффект должен был служить фоном и поэтому требовалось чтобы он был как можно менее ресурсоемким.

Я не изобретаю никакие новые приемы оптимизации, я лишь приведу пример их применения на практике. Для начала я поискал в интернете готовые решения и наткнулся на решение, которое визуально меня устраивало: http://www.lemlinh.com/flash-source-starfield-generator/. Открыв код мы обнаружим, что эти Звездочки — это MovieClip’ы (крик ужаса за кадром). Каждый флеш-разработчик понимает, что кроме отображения «точки» на каждую «Звездочку-MovieClip» вешается куча свойств и методов класса MovieClip. И чем больше звезд — тем хуже. Я ничего не хочу сказать об авторе — просто для нам требуется другое.

Ясно, что в такой ситуации прийдется сделать все с самому и с нуля. Ничего гениального в нашей реализации нет:

  1. создать массив точек stars:Array
  2. в каждом кадре:
  • …просчитать новое положение каждой точке по суперсложной математической формуле (умножение).
  • …нарисовать каждую точку на экране.

Сразу оговорюсь, я использую собственный метод получения случайного значения в классе Tools.Utils.as:

public static function rand(lowValue:Number, hightValue:Number, round:Number = 1):Number{
	return lowValue + Math.floor(Math.random() * (hightValue-lowValue)*round)/round;
}

Реализуем наш нехитрый алгоритм и получаем класс StarFieldBad.as. Код нам не важен. Но именно такой код я написал бы до того, как начал закомиться с методами оптимизации когда.

Звезд у нас будет меньше чем на небе, создадим их всего 2000 (реально их хватит 100-200, но нам для теста нужно побольше):

background = new StarFieldBad(_game.STAGE.stageWidth, _game.STAGE.stageHeight, 2000);
addChild(background);

Выполнение инициализации не является критическим параметром, т.к. создание объекта делается не часто. Но все же среднее время создания: 7,42ms.

Критическим показателем в нашем случае является время выполнения кадра: 9,1095ms.

Вроде бы и подход не самый страшный, и звезды не создаются каждый раз, но на самом деле это все не верх совершенства. Итак, пойдем по порядку.

bitmapData.lock()

В коде не хватает использования метода lock() и unlock(). Добавляем их. (Подробнее «Работа с пикселами» на сайте Adobe.)

В итоге время выполнения стало: 8,9753ms. (Печально, маленький прирост)

Цикл FOR

...for (var i:int = 0; i < stars.length; i++) // Проходимся по всем звездам...

Цикл при каждом проходе обращается к объекту и его свойству. Заодно заменяем цикл for на цикл while:

var i:uint = starsCount;// Итератор звезд
while ( --i > -1)// Проходимся по всем звездам
...

Время выполнения: 7,5379ms. (Удивительно большой прирост)

По возможности избегайте использования оператора квадратной скобки

Например у нас внутри циклов происходят подобные операции:

stars[i].x += (stars[i].x - fieldWidth/2) * 0.01;
...

Заменяем оператор квадратные скобки на ссылку в локальной переменной (имеет смысл, если мы обращаемся к переменной много раз):

var star:Point;// Ссылка на мат. модель звезды
while ( --i > -1)// Проходимся по всем звездам
{
	star = stars[i];
	star.x += (star.x - halfWidth) * 0.01;
	...

Время выполнения: 4.4279ms. (Невероятно большой прирост. Видимо, ещё сказывается большой объем массива)

По возможности используйте класс Vector вместо класса Array

Заменяем Array на Vector.<Point>. «В проигрыватель Flash Player 10 добавлен класс Vector, который обеспечивает более быстрый доступ для чтения и записи, чем класс Array.» (источник)

stars = new Vector.&lt;Point&gt;(starsCount, true);

Время выполнения: 3.0963ms.

Избавляем Flash от ненужных повторяющихся вычислений

Можно заранее просчтитать значения таких выражений как «fieldWidth / 2» на «halfWidth» или «fieldHeight+offset» на «fieldWidthWithOffset» и сохранить их в переменные, тем самым избавив флеш от ~20000 лишних операций деления и сложения(это в нашем примере) за кадр:

public function StarField(width:int, height:int, count:uint)
{
	fieldWidthWithOffset = fieldWidth = width;
	fieldHeightWithOffset = fieldHeight = height;
	
	fieldWidthWithOffset += offset;
	fieldHeightWithOffset += offset;

	halfWidth = fieldWidth / 2;
	halfHeight = fieldHeight / 2;
	...

Время выполнения: 2.9642ms. (ну, большого прироста никто и не обещал)

Другие приемы оптимизации

Далее я опущу описание других методов, которые мало сказались на производительности (но это не значит, что нужно ими пренебрегать!):

  • Определение переменных вне цикла.
  • Встраивание кода для уменьшения числа вызовов функций в коде.

В итоге время выполнения скрипта было уменьшено с 9,7753ms до 2.9405ms. А это более чем в 3 раза! И время инициализации сократилось в 2 раза — с 7,42ms до 3,26ms.

Файл Tools.Utils.as.

Исходный файл StarFieldBad.as.

Итоговый файл StarField.as

Эти и подобные методы оптимизации следует научится использовать сразу, чтобы в последующем не пришлось проводить оптимизацию кода. Всем творческих успехов!

Пару ссылок по теме:

Оптимизация производительности для платформы Flash Platform

http://www.rozengain.com/blog/2007/05/01/some-actionscript-30-optimizations/

И конечно, Google

2010.09.22