Личные наблюдения по оптимизации

Список всего что я нашел (столкнулся) в процессе написания игры.

(Будет пополняться, буду благодарен если кто нибудь прокомментирует о причинах таких поведений)
1.
Если делаем так, получаем рост памяти с последующей периодической очисткой.
point.x += numberCordX + _rtX;

Если так, то получаем прямую и никаких скачков
point.x += Number(numberCordX + _rtX);


2.
Если передавать delta:Number, получим то же что и в п.1
tempObj.update(delta);

Если передавать delta:int, все отлично скачков нет
tempObj.update(delta as int);

UPD Здесь суть не в типе (можно и number главное чтоб было целое, а не дробное), а в том что передаем не delta=0,033, например, а delta=33 и уже по месту *0,001.

UPD2
Тест пункта 2.
Space — переключение (delata:INT или delata:Number), UP- добавить 500 об, DOWN — удалить 500.
megaswf.com/serve/1187366

package TEST
{
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.events.KeyboardEvent;
        import flash.text.TextField;
        import flash.text.TextFieldAutoSize;
        import flash.ui.Keyboard;
        import flash.utils.getTimer;
        import utils.SWFProfiler;
        
        public class test extends Sprite
        {
                private var _deltaINT:int = 0;
                private var _deltaNUMBER:Number = 0;
                private var _maxDeltaTime:int = 60;
                private var _lastTick:int = 0;
                private var testINT:Boolean = false;
                private var container:ObjectController;
                private var _text:TextField;
                private var n:int = 500;
                private var _testmode:String = "delta:NUMBER (=0.03)";
                
                public function test()
                {
                        if (stage)
                        init();
                        else
                        addEventListener(Event.ADDED_TO_STAGE, addedToStageListener);
                }
                private function addedToStageListener(e:Event):void
                {
                        removeEventListener(Event.ADDED_TO_STAGE, addedToStageListener);
                        init();
                }
                private function init():void
                {
                        SWFProfiler.init(stage, this);
                        container = new ObjectController();

                        stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
                        stage.addEventListener(Event.ENTER_FRAME, enterFrameListener);

                        _text = new TextField();
                        _text.textColor = 0xCE0000;
                        _text.x = 640 / 2 - 50;
                        _text.background = true;
                        _text.height = 50;
                        _text.width = 220;
                        _text.selectable = false;
                        _text.backgroundColor = 0x000000;
                        //_text.autoSize = TextFieldAutoSize.LEFT;
                        _text.text = ("TESTMODE: " + _testmode + "\n" + "Object= " + container.objectsLng) as String;
                        _text.y = 150;
                        addChild(_text);
                }
                private function keyDownHandler(e:KeyboardEvent):void
                {
                        if (e.keyCode == Keyboard.SPACE)
                        {
                                testINT = !testINT;
                                if (testINT)
                                {
                                        _testmode = "delta:INT  (=30)";
                                }
                                else
                                {
                                        _testmode = "delta:NUMBER (=0.03)";
                                }
                        }
                        if (e.keyCode == Keyboard.UP)
                        {
                                for (var i:int = 0; i < n; i++)
                                {
                                        container.add(new Unit());
                                }
                        }
                        if (e.keyCode == Keyboard.DOWN)
                        {
                                container.remove(n);
                        }
                        _text.text = ("TESTMODE: " + _testmode + "\n" + "Object= " + container.objectsLng) as String;
                }

                private function enterFrameListener(e:Event):void
                {
                        if (!testINT)
                        {
                                _deltaNUMBER = 0.03;  //(getTimer() - _lastTick) / 1000;
                                container.update_Num(_deltaNUMBER);
                        }
                        else
                        {
                                _deltaINT = 30;      //(getTimer() - _lastTick);
                                container.update_INT(_deltaINT);
                        }
                        //_lastTick = getTimer();
                }
        }

}


package TEST
{       
        public class ObjectController extends Object
        {
                public var objects:Array = [];
                private var tempObj:Object;
                private var i:int = 0ж

                public function ObjectController()
                {
                }
                public function add(obj:Object):void
                {
                        objects[objects.length] = obj;
                }
                public function remove(n:int):void
                {
                        objects.splice(objects.length - n, n);
                }
                public function update_INT(delta:int):void
                {
                        while (i<objects.length)
                        {
                        tempObj = objects[i as int];
                        tempObj.update_INT(delta);
                        i += 1;
                        }
                        i = 0;
                }
                public function update_Num(delta:Number):void
                {
                        while (i<objects.length)
                        {
                        tempObj = objects[i as int];
                        tempObj.update_Number(delta);
                        i += 1;
                        }
                        i = 0;
                }
                public function get objectsLng():int
                {
                        return objects.length;
                }
        }
}


package TEST {
        public class Unit {
                public function Unit() 
                {       }
                public function update_INT(delta:int):void
                {       }
                public function update_Number(delta:Number):void
                {       }
        }
}
  • +4

Комментарии (61)

0
Мог бы ты пояснить, что это — shoot.frameXs[n][_rotPos]
?
0
Так то не в этом суть, а в принудительном приведении типов. Я у себя заметил так же местами более плавную анимацию, вообще лучше делать чем не делать принудительное привидения типа
0
> Я у себя заметил так же местами более плавную анимацию
Ну уж точно не из за приведения типов.
Профита, что-то около нуля, только код замусоривать.
0
у меня было легкое дерганье анимации на фпс 60 до привидения, сейчас его нет
+2
Причин рывков анимации может быть миллион. И приведение типов не одна из них ;).
0
Насчет дерганий не знаю, но то что сборщика мучать перестаем и память более стабильно держится это точно. (Проверялось при >1000 объектов)
0
Если delta сделать вещественной, а не целой, то это даст прирост производительности заметно превышающий прирост от приведения типов. Т.к. у тебя в месте использования дельты будет лишнее умножение и тот же самый каст int в Number (при умножении). Хотя все равно получается экономия на спичках.
0
Это взято из статьи про растровый рендер. Просто для примера. Суть, как сказали, в приведении. А так 2 мерный массив координат.
+1
Тут нужно вызывать людей разбирающихся в ассемблере AVM, там корень причин подобных оптимизаций)
+1
Если следовать пункту 2, то выходит что при вызове любого метода необходимо передаваемые параметры приводить к нужному типу!? Для меня, приведенные тут примеры, кажутся весьма сомнительными и не пригодными для использования в целях оптимизации.
0
Просто когда я передавал delta дробное, начинались скачки памяти. Еcли передавать delta целое и потом *0.001, то память стабильна.
www.ant-karlov.ru/TowerDefence9-keshirovanie-obektov.html
В последней демке в профайлере память растет и очищается. Вот от такого избавляет.
+1
Если честно, то я не нахожу логического объяснения тому почему в этом месте может течь память. Но если это действительно так, то это очередной повод помянуть адобовцев добрым словцом. Приведение delta к int и обратно — это не самый удобный и красивый костыль, к сожалению.

Кстати, память течет в дебаг версии плеера или в релизном? А то ведь тут может быть все по разному, включая и сами версии плееров.
0
В обоих версиях. Приведение это уже перестраховка. Только что еще раз проверил.
Пробовал и с Number только без дробной части, в разы меньше но есть (10-100 кб в сек), при int нет течи вообще. Поэтому лучше передавать целое и умножать.
0
Утечка — это когда память забивается и не освобождается во время работы. В данном случае это конечно не столько утечка, сколько по какой-то причине не рациональное использование памяти.

Можно сделать тест чтобы его можно было запустить на разных компьютерах с разными ОС, браузерами и версиями плееров чтобы говорить наверняка о том, что это проблема Flash, а не какой-нибудь частный случай?
0
Добавил тест.
0
Посмотрел тест, на моей машине никакой разницы в работе памяти нет, будь то Number или int (память в течении долгого времени не занимается и не освобождается). Склоняюсь все же к тому, что это частный случай. Завтра еще посмотрю на другой машине с Flash Player 11.

MacOS SL, Safari, Flash Player [release] 10.3.
0
Извиняюсь, забыл добавить объекты :) Теперь вижу проблему.
Эти колебания памяти выглядят страшно на фоне 7-8 мб используемой оперативной памяти, когда же игра разрастется до 60-100 мб используемой оперативной памяти, то эти колебания будут почти не заметны на фоне других более сложных объектов. Иными словами: сборщик мусора во время любой игры будет вызываться в любом случае, вопрос только в том сколько он за одну итерацию вычистит мусора… Если следовать такой логике то прийдется кэшировать все на свете, включая локальные переменные внутри методов — ведь они тоже будут мусором после того как их работа закончилась.

А вообще конечно не приятно что такая проблема существует :(
0
В том то и проблема, что в игре весом 200мб счет уже идет на десятки мб туда сюда (сейчас точно не скажу сколько, вроде 50мб очищает постоянно). Что при этом еще и неизбежно тянет вниз немного фпс. Поэтому при большом кол-ве объектов исп везде приведение и int по возможности. Немного радует что проблема не в моих руках :)
0
Хорошо. А что если переменную delta сделать публичной статической переменной того класса в катором она рассчитывается и обращатся к ней непосредственно через указатель класса, а не передавать ее в качестве аргумента метода?

Если это решение сработает, то это будет намного оптимальнее чем приведение к типу int и обратно.
0
В as3 обращение к статическим полям медленное, насколько я помню.
Но даже если бы было быстрым, кэшировать dt маразм.
0
Тут нет приведения, при int просто в конечном классе (Unit в нашем случае) умножаем 0.001. Лишнее действие ни как не сказывающиеся на производительности (учитывая что избавились от странного поведения памяти).
0
var delta:int;
public function enterFrameHandler(event:Event):void
{
_delta = (getTimer() — _lastTick);
tempObj.update(delta);
}
класс tempObj
public function update(delta:int):void
{
//потом где надо использовать делаем так например
unit.x+=move*(delta*0.001);
}
Или рассчитывать при целой delta.
Но я не вижу просадки фпс при умножении. (при >1500 юнитов)
0
Сегодня сделаю и добавлю сюда к теме.
+1
Вообще Jackson Dunstan говорил о подобных оптимизациях, даже копаясь в ассемблере он не смог объяснить приросты статья. В комментариях не копался, сейчас их там 48, может уже дали оъяснение
+1
Так же выставили этот тест на wonderfl
flash 11.0.1.152, chrome 14.0.835.202, win 7 64 core 2 duo 2.2GHz
0
забыл сами результаты )
Type,No Cast Time,Cast Time
Array,141,62
Vector,109,63
Dictionary,358,303
Object,367,307
Dynamic Class,338,281
0
Явное приведение нетипезированных массивов это не тоже самое, что и Number в int (простые типы).
0
Спасибо за ссылку!
Статья (да собственно и комментарии, и ссылки на другие статьи) очень интересные.
0
блог периодически обновляется новыми тестами, я даже думал в одно время заняться переводами сюда, да уж больно много статей))
0
Блог действительно классный (главный плюс к хорошим статьям — толковые комментарии). Я на рсс подписался :)
0
Добавил пояснение UPD ко 2 пункту.
0
Добавил обещанный тест. см. UPD2
0
Сделай релизную сборку.
0
Если не сложно, добавь простейший индикатор того, что используется в данный момент. И желательно сделать на ± добавлять/убирать по тыще объектов, чтобы можно было оценить эффективность.
0
Сделал релизную (это ничего не меняет). Сделал надпись по центру какая используется дельта (пример в скобках), вверх/вниз добваить и убавить кол-во объектов.
0
200k объектов по памяти абсолютно одинаково. FPS немного выше с целой дельтой, но подозреваю, что это из за структуры теста. Код в студию! :)
0
у меня и в 10fp debug и в 11fp (не дебаг) в браузере одинаково при Number скачет память.
0
Добавил код
0
Нечестный тест. Очевидно что в случае Number лишнее деление на тыщу, которое будет один раз на каждый глобальный update
_deltaNUMBER = (getTimer() — _lastTick) / 1000;

В то время как тут деления нету:
_deltaINT = (getTimer() — _lastTick);

Но зато в Unit::update_INT пусто, хотя должно быть одно лишнее умножение на 1000 для КАЖДОГО объекта за один глобальный update.
0
Хотя все равно такая мелочь, что в масштабе всей игры можно не считать :).
0
Заменил на константы )) Теперь все одинаково. Реакция прежняя. Я первым делом проверял не из-за динамической ли delta это и попробовал константы.
0
Без умножения в update_INT все равно не честно.

Но память скачет в обоих случаях всех тестов одинаково. На моей ЭВМ, по крайней мере.
0
ведь нет умножения, я просто написал 0.03 и все.
0
Я имел ввиду, что в реальной программе объекты не могут использовать целый dt. И где нибудь внутри update_INT всегда будет преобразование целого dt в вещественное да еще с умножением.
0
Да, в классе Unit будет умножение *0.001 чтобы получить дробное. Т.е. просто переносим эту операцию из главного класса (test) в конечные классы (Unit), где delta непосредственно используется.
На практике я не вижу с моим кол-ом объектов просадки из-за лишних умножений. А вот из-за передачи Number вижу что происходит с памятью.
0
Тесты переключаешь на пробел? (на всякий спрасил)
у меня при INT на 4кб вверх вниз, раз в несколько сек. (что не заметно почти даже тут), а при Number пила вверх\вниз с размахом в 1мб.
0
Та же фигня.
Только при Number у меня по 4Мб скачки.
Чей-то Number пилит там ;D
0
Тут Unit пустой. При реальных объектах с вычислениями все еще ухудшается. С малым кол-ом объектов это конечно не важно и на оптимизацию можно забить. Но если объектов много и они не просто шарик, а вполне себе объект с 1000 строчек кода внутри и вякими плюшками визуальными, то память лихорадит еще сильнее. К тому же подобных ситуаций обычно не 1, а с десяток может быть.
0
У меня такой только вопрос, это сколько объектов в вашей игре? Просто у меня в тесте какое-то шевеление можно заметить только уже не десятках тысяч объектов. Ну пусть у меня комп навороченный. Но пусть туча юнитов на экране и все стреляют пульками… эти копейки все равно незаметно будет на фоне прорисовки, логики итд. Мне кажется не там копейки ищете.
+1
комп e2180(1.8),2гб память. У меня в тесте и на 1500 заметно все, просто не так сильно как при 200к.
В игре у себя тестил на 1630 объектах (у них дофига всякой логики у каждого внутри) + еще всякие вспомогательные менюшки/панельки жрут. Это нашлось случайно )), при оптимизации визуализации. Просадки по фпс при очистке не большые, но лучше перестраховаться. Плюс я стараюсь каждый фпс вытянуть лишний, с таким кол-ом юнитов всегда пригодиться. Когда память в виде прямой, а не скачет туда сюда мне как-то легче спиться )).
+1
Не исключено, что я ошибаюсь, но мне спокойнее, когда фпс и память идут ровно, а не скачут. Даже если это «ровно» — верхняя уровень скачков (на деле это нижний уровень).
0
Если уж держаться за копейки, то проще сделать update пустым и держать глобальный dt, а не спускать его по всей иерархии.
0
на фпс да, как ни странно мало влияет. А вот постоянные очистки десятков мб памяти уже не копейки. И чем более сложный код и больше объектов, тем хуже.
Любая оптимизация имеет смысл при достижении пределов флеша, 150 объектов держит у меня и в векторе, без всяких оптимизаций вообще и пофиг как там скачет память. Вот только мне надо в 10 раз больше и без различных шаманств уже не обойтись.
0
Ну скачет, и Бог с ней :). Предел флеша ведь не в памяти, а в проседании FPS.
+1
так из за очистки памяти бывают скачки фпс, особенно когда за раз много очищается
0
В сабжевом тесте нет никаких скачков.
0
Кстати, п.1 во время написания статьи про рендеринг нашелся. Он просаживал на 5 фпс при очистке что уже весьма существенно. Тоже память не стабильна была.
0
У меня даже на 500 объектов колбасит на два мегабайта, что как-то через чур странно.
В байткоде вызовы с Number и int ничем не отличаются.
При этом собрав тест с вызовом функции с Number-аргументом на 1М итераций, каких-то «видимых» колебаний вообще не показывает, вот такой код выполняется по энтерфрейм:
private function update(e:Event):void 
{
        output.text = System.totalMemory.toString();
        for (var i:int = 0; i < 1000000; i++) 
        {
                any(0.033);
        }
}
                
private function any(n:Number):void 
{
        return;
}
0
в 1 классе да, а тут мы передаем из главного test, через ObjectController в Unit.
Все дело в строчке objects[i].update_Number(delta), без нее нет скачков (но нам то надо передать), т.е. скачки начинаются на передаче из ObjectController в Unit.
0
даже в таком раскладе ничего не показал:
private function update(e:Event):void 
{
        var stack:Array = [new other()];
        output.text = System.totalMemory.toString();
        for (var i:int = 0; i < 100000; i++) 
        {
                other(stack[0]).any(0.033);
        }
}

на несколько десятков килобайт дребезжит, но это нормальная ситуация для такого количества вызовов.
0
Что профайлер по памяти показывает?
Создаются новые объекты?
  • ryzed
  • ryzed
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.