Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > caseyryan

Оценить эту запись

Самое слабое место Dragon Bones - класс TransformUtil

Запись от caseyryan размещена 02.02.2016 в 09:17

На днях сделал очередную предрелизную сборку игры, в которой у меня очень активно используется драгон бонс для анимации всего и вся. И, к моему величайшему расстроству, при появлении персонажей на экране, FPS довольно сильно начинает провисать. Если до их появления стабильно 60, то после появления 2 - 3 персов он падает до 35 - 40, а то и ниже. И это на довольно мощном устройстве Lenovo S90.
Еще больше огорчал тот факт, что игра уже довольно хорошо оптимизирована. Все рекомендации описанные здесь http://wiki.starling-framework.org/m...e_optimization учтены, а что касается простого флеша, тем более. Сборку для проверки всегда делаю релизную.
Решил детально поковырять игру скаутом. И пришел к выводу, что все провисания FPS происходят не из-за отрисовки текстур или их загрузки в видеопамять (как я изначально предполагал), а именно из-за математиеских расчетов, которые проивходят в драгон бонсе.
Сначала вижу, что провисает на 10 миллисекунд на top-level коде при вызове Number._convert, а дальше при вызове методов из класса dragonBones.utils.TransformUtil

Проблему мне решить удалось. И вот что я сделал:
1) Увидев в этом классе множество обращений к Math.PI, я подумал "а какого фига? это же константа" Известно же, что статики в as3 (да и не только) - штука довольно медленная. Поэтому просто заменил все вызовы этой константы на ее числовое значение 3.141592653589793
2) Так как компилятор в FD у меня ASC 2.0, то я сразу полез в Project - Properties - Compiler Options и там поменял значение свойства Inline Functions на True
3) Дальше каждому статическому методу в этом классе дописал метатег [Inline]
пример для тех кто не знает как это делается:
Код AS3:
[Inline]
public static function transformToMatrix(transform:DBTransform, matrix:Matrix):void
{
	matrix.a = transform.scaleX * Math.cos(transform.skewY)
	matrix.b = transform.scaleX * Math.sin(transform.skewY)
	matrix.c = -transform.scaleY * Math.sin(transform.skewX);
	matrix.d = transform.scaleY * Math.cos(transform.skewX);
	matrix.tx = transform.x;
	matrix.ty = transform.y;
}
Сделал сборку файла и открыл полученный swf декомпилятором. Убедился, что все эти методы действительно "заинлайнились", и снова запустил игру для проверки скаутом.

Теперь провисания были уже далеко не такие серьезные. Но они все еще наблюдались кое где. И снова причиной был Dragon Bones. И причина крылась в том, что в методе matrixToTransform, есть вот такая (довольно странная, как по мне) конструкция:

Код AS3:
var skewXArray:Array = [];
skewXArray[0] = Math.acos(matrix.d / transform.scaleY);
skewXArray[1] = -skewXArray[0];
skewXArray[2] = Math.asin(-matrix.c / transform.scaleY);
skewXArray[3] = skewXArray[2] >= 0 ? 3.141592653589793 - skewXArray[2] : skewXArray[2] - 3.141592653589793;
 
if (Number(skewXArray[0]).toFixed(4) == Number(skewXArray[2]).toFixed(4) || 
	Number(skewXArray[0]).toFixed(4) == Number(skewXArray[3]).toFixed(4))
 
{
	transform.skewX = skewXArray[0];
}
else 
{
	transform.skewX = skewXArray[1];
}
Как ни странно, вот эти Number(skewXArray[0]).toFixed(4) и есть самое слабое место драгон бонса.
Сначла число конвертируется в строку, (что, по замерам скаута, занимает целых 10 миллисекунд!!!, и это очень много для одного кадра) а потом еще и сокращается до 4 знаков после запятой.

Решил попробовать избавиться от этого места, и просто убрал конвертации.
Код AS3:
if (skewXArray[0] == skewXArray[2] || 
skewXArray[0] == skewXArray[3])
Пока я не заметил разницы, но уверен, что теперь эта конструкция работает не верно, так как числа явно получаются не совсем равные. Лучшей идеи мне пока не пришло. Если кто-то подскажет её, буду очень благодарен.


Вот, собственно и все! Теперь FPS не провисает ниже 59 - 60 вообще. Даже если на экране 5, 6 или даже 8 врагов (у которых довольно сложные анимации)
Всего комментариев 15

Комментарии

Старый 03.02.2016 10:14 Dmitriy154 вне форума
Dmitriy154
дописал метатег [Inline] - а для чего это делается?
Старый 03.02.2016 11:20 Tails вне форума
Tails
 
Аватар для Tails
10 mc как то нереально много.
Но вообще, производить математические вычисления через переводы в строки, как по мне - идея очень странная.

Попробуй сделать ещё одну функцию для кастомного округления:
Код AS3:
return Math.floor(value * 10000) / 10000
И заинлайнить её. Уж точно не должно быть 10 mc.

пс.
Ещё вот этот массив skewXArray, если он только в функций используется, его можно создать один раз в какой нить статической переменной класса. Зачем каждый раз его создавать и удалять?

А лучше вообще это через переменные функций сделать, накой там массив?
Кароч, если это узкое место в программе, а там такая вакханалия творится... написавшему это нужно по рукам настучать.
Обновил(-а) Tails 03.02.2016 в 11:44
Старый 03.02.2016 14:23 alatar вне форума
alatar
 
Аватар для alatar
Цитата:
1) Увидев в этом классе множество обращений к Math.PI, я подумал "а какого фига? это же константа" Известно же, что статики в as3 (да и не только) - штука довольно медленная. Поэтому просто заменил все вызовы этой константы на ее числовое значение 3.141592653589793
ASC 2.0 делает это автоматически для всех констант. Прежде чем утверждать что-то "известное же", стоило бы и проверить.
Старый 03.02.2016 22:16 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Если бы не увидел коммент alatar'а, то мне бы стало любопытно, почему не заинлайнить свой MyMath.PI(). Было бы.. красивее.

Ну и, конечно, если не скован NDA – пиарь игрушку!
Старый 04.02.2016 00:31 kutuzov вне форума
kutuzov
 
Аватар для kutuzov
Код AS3:
skewXArray[0] * 10000 | 0 == skewXArray[2] * 10000 | 0
Старый 05.02.2016 09:13 caseyryan вне форума
caseyryan
 
Аватар для caseyryan
Цитата:
Прежде чем утверждать что-то "известное же", стоило бы и проверить.
Про подстановку констант в ASC 2.0 не знал, спасибо за инфу. А утверждал, что обращение к статикам в ас3 медленное. И это действительно так.

Цитата:
Попробуй сделать ещё одну функцию для кастомного округления:
Код AS3:
return Math.floor(value * 10000) / 10000;
И заинлайнить её. Уж точно не должно быть 10 mc.
Наверное это будет по-быстрее, чем тот способ у китайца. Но все равно floor не самый быстрый метод



В общем, оказалось, что Dragon Bones 4.3.2 сам по себе намного тормознее версии 3.0. Обратно откатился на версию 3.0, и с учетом оптимизаций теперь игра просто летает. Скаут показывает прирост производительности почти на 40%!!! по сравнению с версией 4.3.2. В среднем задержки в тех же участках игры с 12 - 13 мс упали до 7 - 8, что не может не радовать). Но самое интересное то, что в старой версии драгона мусора копилось гораздо меньше, чем в более новых. Это как раз тот случай, когда обновление делает программу хуже, а не лучше
Старый 13.02.2016 16:18 dimarik вне форума
dimarik
 
Аватар для dimarik
Обращение к статикам становится очевидным на миллионах обращений. Ну и есть еще гении в Dragon Bones, что тут скажешь.
Старый 13.02.2016 16:27 caseyryan вне форума
caseyryan
 
Аватар для caseyryan
Цитата:
очевидным на миллионах обращений
Миллионам? Статик статику рознь. Иногда гораздо меньше. Здесь обращение становится очевидным уже при нескольких сотнях обращений в за кадр.
Старый 25.02.2016 20:54 dimarik вне форума
dimarik
 
Аватар для dimarik
Нет, конечно. За такой delay виновато не само по себе обращение к статик методу, а то, что в нем происходит.
Старый 26.02.2016 08:52 caseyryan вне форума
caseyryan
 
Аватар для caseyryan
Как тогда объяснить то, что то же самое содержимое, но в инлайне, дает прирост скорости раз в 6?
Старый 26.02.2016 12:54 СлаваRa вне форума
СлаваRa
 
Аватар для СлаваRa
тесты в студию
Старый 26.02.2016 16:42 caseyryan вне форума
caseyryan
 
Аватар для caseyryan
Цитата:
тесты в студию
Код AS3:
public function MathCrap() {
 
	var tf:TextField = new TextField();
	tf.width = 800;
	tf.height = 600;
	addChild(tf);
 
	var number:Number = -14;
	var i:int = 0;
	var result:Number = 0;
	var time:int = 0;
 
	time = getTimer();
	for (i = 0; i < 10000000; i++) {
		result = Math.abs(number);
	}
	tf.appendText("Math.abs() " + (getTimer() - time) + "\n"); 
	time = getTimer();
	for (i = 0; i < 10000000; i++) {
		result = abs(number);
	}
	tf.appendText("Inline abs " + (getTimer() - time) + "\n");
	tf.appendText("CEIL >>>");
	number = 1.5;
	time = getTimer();
	for (i = 0; i < 10000000; i++) {
		result = Math.ceil(number);
	}
	tf.appendText("Math.ceil() " + (getTimer() - time) + "\n");
	time = getTimer();
	for (i = 0; i < 10000000; i++) {
		result = ceil(number);
	}
	tf.appendText("Inline ceil " + (getTimer() - time) + "\n");
 
}
[Inline]
public static function abs(value:Number):Number {
	return value > 0 ? value : -value;
}
[Inline]
public static function ceil(value:Number):int {
	return int(value) + 1;
}
Результат:
Цитата:
Math.abs() 528
Inline abs 85
CEIL >>>Math.ceil() 549
Inline ceil 55
Это в релиз билде. В отладочном результаты примерно одинаковые, но инлайн все равно чуточку быстрее.
Особенно это заметно в реальной игре, которая запускается на мобиле. Вот где действительно требуется отвоевать каждую миллисекунду
Старый 27.02.2016 04:11 СлаваRa вне форума
СлаваRa
 
Аватар для СлаваRa
а без Inline будет одинаково, и часто у тебя десять миллионов вызовов abs подряд?
Обновил(-а) СлаваRa 27.02.2016 в 10:24
Старый 27.02.2016 15:16 dimarik вне форума
dimarik
 
Аватар для dimarik
Согласен на все сто. Вызов метода отнимает много времени. Туда-сюда, пятое-десятое, пока в стек положишь адрес возврата, пока создашь объект активации, вот и времечко пролетит. Ну а выходить тоже нужно, тоже время.

Но я не об этом. К статикам тормознее обращение по сравнению с методами объекта. Именно эта разница проявляется на миллионах итераций. Вот и все.
Старый 27.02.2016 16:25 caseyryan вне форума
caseyryan
 
Аватар для caseyryan
Цитата:
а без Inline будет одинаково, и часто у тебя десять миллионов вызовов abs подряд?
Все равно будет быстрее. Хоть и не намного. Разница примерно такая, статик 1300 мс, просто метод без инлайна 1100.

Слав, как-то странно ты рассуждаешь. Конечно там нет 10 миллионов вызовов, но несколько тысяч есть. Добавь к этому то, что происходит в самом методе (там есть еще вызовы других статиков) и этот подход реально поможет выиграть 1 - 2 миллисекунды за кадр. Это очень много при высоконагруженных мобильных приложениях. Тут без преувеличения приходится выжимать каждую миллисекунду. Провиснет ФПС хотя бы на 5 кадров и это уже неприятно. А если несколько кадров подряд будут провисания, и ФПС упадет кадров на 15, то плохие оценки от игроков обеспечены.
Подход с инлайнами - реально хороший шаг в оптимизации. Не единственный, но принебрегать им нельзя.
Я вообще не сильный фанат оптимизаций, и никакой шизы по этому поводу у меня нет. Обычно я начинаю что-то оптимизировать уже тогда, когда начинает тормозить. И это как раз тот случай
 

 


Часовой пояс GMT +4, время: 03:44.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.