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

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

Как что-то сделать, при этом ничего не делая
Оценить эту запись

Пул объектов

Запись от gloomyBrain размещена 31.08.2011 в 23:27
Обновил(-а) gloomyBrain 28.09.2011 в 22:15 (Исправил и дополнил)

Перед каждым программистом, так или иначе, периодически встают задачи оптимизации. Побуду немного Капитаном Очевидность - программы потребляют всего две вещи: память и процессорное время. Сегодня я хотел бы рассказать об одном из способов экономить память - объектном пуле.


Для чего нам это нужно?

Тут все достаточно просто. Каждый раз, когда мы создаем новый объект
Код AS3:
new MyCustomClass()
мы просим систему выделить нам память. После того, как память найдена и предоставлена, мы заполняем ее необходимыми данными. После этого мы запускаем конструктор. А конструктор делает еще миллион всяких вызовов и создает кучу объектов. И все это тратит драгоценные процессорное время и память.
Пул объектов позволит нам не отдавать ненужные объекты сборщику мусора (GC), а, вместо этого, положить их на полочку, откуда, в случае необходимости, мы их снова заберем. То есть мы не будем искать память, не будем вызывать конструктор... Ничего не будем делать. Нужен объект - вот он, забирай!


Хватит теории, как это работает?

Сразу оговоримся - реализации могут быть разные, все со своими плюсами и минусами. Если у нас миллионы объектов, которые рождаются и погибают пачками по несколько тысяч за раз - выгоднее сначала насоздавать объектов, запихнуть их в пул, а потом уже забирать сколько влезет.
Если объектов немного или они, по крайней мере, не создаются тысячами за раз, то есть смысл генерировать их по одному.
Ну, и вправду, хватит теории. Так как что и как придется делать мы заранее никогда не знаем, давайте попробуем описать наш пул интерфейсом. У меня вышло примерно так:
Код AS3:
package utils.pooling {
 
	/**
	 * Интерфейс для пула объектов
	 * 
	 * @author gloomyBrain
	 */
	public interface IPool {
 
		/**
		 * Добавить объект в пул
		 */
		function addItem(value:Object):void;
 
		/**
		 * Получить следующий объект из пула
		 */
		function getItem():Object;
 
	}
 
}
Так просто? Да, именно так! По сути дела, все что нужно тут уже есть - мы можем попросить пул отдать объект, когда он нам нужен и мы можем вернуть объект обратно в пул, если нужды в нем больше нет.


Ну а где же сам пул?

Вот он:
Код AS3:
package ugo.utils.pooling {
 
	import flash.errors.IllegalOperationError;
	import flash.utils.Dictionary;
	import ugo.core.IFactory;
 
	/**
	 * Самый простой пул объектов
	 * 
	 * @author gloomyBrain
	 */
	public final class GenericPool implements IPool {
 
		private var _pool:Vector.<Object>;
		private var _linker:Dictionary;
 
		private var _factory:IFactory;
		private var _initCount:uint;
 
		public function GenericPool(factory:IFactory, initCount:uint = 1) {
 
			super();
 
			if (!factory) throw new ArgumentError("Параметр является обязательным!");
			_factory = factory;
			_initCount = (initCount > 0) ? initCount : 1;
 
			_pool = new Vector.<Object>();
			_linker = new Dictionary();
 
		}
 
 
		/* INTERFACE ugo.utils.pooling.IPool */
 
		/**
		 * Добавить объект в пул
		 */
		public function addItem(value:Object):void {
 
			if (!value) throw new ArgumentError("Параметр не должен быть нулевым!");
			if (value in _linker) throw new IllegalOperationError("Объект уже находится в этом пуле!");
 
			if (!(value is _factory.instanceType)) throw new IllegalOperationError("Объект не может быть добавлен в пул из-за несоответствия типов!");
 
			this.addItemInternal(value);
 
		}
 
		/**
		 * Получить следующий объект из пула
		 */
		public function getItem():Object {
 
			var result:Object = null;
 
			if (_pool.length) {
 
				result = _pool.pop();
				delete _linker[result];
 
			}else {
 
				var len:int = _initCount - 1;
				while (len--) this.addItemInternal(_factory.newInstance());
 
				result = _factory.newInstance();
			}
 
			return result;
		}
 
		private function addItemInternal(value:Object):void {
 
			_pool.push(value);
			_linker[value] = true;
		}
 
	}
 
}
Вот и все, о чем я хотел рассказать. Надеюсь, это подскажет Вам новые идеи для создания крутых flash-приложений и поможет Вам захватить мир =)

PS
IFactory - это еще один интерфейс для генерации новых объектов. Он нужен для того, чтобы запускать конструктор объекта с нужными параметрами. Выглядит он примерно так:
Код AS3:
package core {
 
	/**
	 * Интерфейс для фабрики объектов
	 * 
	 * @author gloomybrain
	 */
	public interface IFactory {
 
		/**
		 * Тип данных экземпляров, возвращаемых фабрикой
		 */
		function get instanceType():Class;
 
		/**
		 * Создает новый экземпляр объекта
		 */
		function newInstance():Object;
 
	}
 
}

PPS
Да, пожалуй, многовато кода... Комментарии приветствуются!

PPPS
thx i.o., добавил Dictionary для быстрой проверки находится ли объект в пуле. Потому как 2 раза один и тот же объект помещать в пул нам незачем.
Размещено в flash.utils
Комментарии 42 Отправить другу ссылку на эту запись
Всего комментариев 42

Комментарии

Старый 01.09.2011 00:12 GBee вне форума
GBee
 
Аватар для GBee
ЗдОрово.
Может имеет смысл добавить в IPoolable метод init() для инициализация каких-либо дефолтных свойств при извлечении из стека.

IFactory меня смущает, пока не знаю чем.
Старый 01.09.2011 00:50 dimarik вне форума
dimarik
 
Аватар для dimarik
Этим?
Код AS3:
function newInstance():Object;
Старый 01.09.2011 00:51 i.o. вне форума
i.o.
 
Аватар для i.o.
По-моему объекту должно быть до фени, можно ли его добавить в пул или нет. Не его это как бы беспокойства
Старый 01.09.2011 01:16 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
@i.o.
Хотелось чтобы объект гарантированно деактиировался и возвращался в пул. Для этого и сделал IPoolable.

@GBee, @dimarik
А что смущает? В AS3 пока что параметризированных типов нет, так что пришлось делать фабрику.
Старый 01.09.2011 01:18 GBee вне форума
GBee
 
Аватар для GBee
Цитата:
Этим?
Угу, вообще кажется лишним. Почему бы просто не отдавать класс. Все равно в этой реализации меня смущает, что объект "теряет" конструктор. Объект, доставаемый из пула, скорее всего настраивается под текущие нужды "хозяина". Таким образом фабрика должна знать, что хочет хозяин, чтобы передать нужные параметры, а если "хозяин" не один. А тут еще фабрика заготовки впрок готовить может.

Не могу не согласиться с i.o.
Старый 01.09.2011 10:37 i.o. вне форума
i.o.
 
Аватар для i.o.
Цитата:
Хотелось чтобы объект гарантированно деактиировался и возвращался в пул. Для этого и сделал IPoolable.
Каким образом это дает гарантию? Класс сам имплементирует геттер и сеттер pool как и метод release.
Т.о. он становится зависиым от специфичного интерфейса IPoolable.
Плюс это его обязывает нести в себе реализацию этого интерфейса, т.е. класс обрастает лишним кодом.
И такой пул не может хранить любые объекты. Ну например классы-структуры "со стороны".
Старый 01.09.2011 11:10 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
@i.o.
Согласен. Но я основывался на мысли о том, что в пул может быть положен только тот объект, который был из него вынут. То есть генератором объектов является пул и он же является единственным местом, куда можно объект вернуть. При этом нужно чтобы объект нельзя было 2 раза поместить в один пул. С одной стороны - да, сеттер пула тут лишний. С другой стороны - как добиться желаемого без него?

@GBee
Класс не отдается потому, что неизвестно заранее с какими параметрами будут создаваться экземпляры. То есть если я передаю класс, то у него точно должен быть конструктор без параметров. Например BitmapData таким образом не запулишь.
Старый 01.09.2011 11:57 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Спасибо i.o. за идею использовать Dictionary. Попробую переписать с дублированием - Dictionary для ппроверок на повторное добавление объекта в пул, Vector для быстрого добавления/удаления из пула.
Старый 01.09.2011 12:08 GBee вне форума
GBee
 
Аватар для GBee
Цитата:
@GBee
Класс не отдается потому, что неизвестно заранее с какими параметрами будут создаваться экземпляры. То есть если я передаю класс, то у него точно должен быть конструктор без параметров. Например BitmapData таким образом не запулишь.
Хм, я про это и спрашивал. Какие параметры будет отдавать Фабрика? Какие-то дефолтные получается. А если разные объекты хотят битмапдат из пула и каждый со своими параметрами.
Старый 01.09.2011 12:19 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Немного переписал пул. Убрал IPoolable нафиг, он тут и правда лишний.

@GBee
Ну вот есть у нас уже написанный класс. И тут мы решили положить его экземпляры в пул. Вот тут нам и понадобится фабрика.
Старый 01.09.2011 12:31 GBee вне форума
GBee
 
Аватар для GBee
:о)) да я не об этом. В примерах давайте. Например танчики. Пусть пульки лежат в пуле. Bullet(x, y, power, angle) x,y - стартовая позиция, power - мощность заряда, angle - угол куда лететь. Танчик 1 просит у пула заряд, а там уже лежит фабричный с дефолтовыми параметрами, а не с теми, что нужны танку. Танчику 2 нужен заряд помощнее и летит в другую сторону, то есть после извлечения заряда из пула его все равно придется настраивать. Получается, что конструктор работает вхолостую, а фабрика нужна для обязательных параметров конструктора (пример BitmapData).
Старый 01.09.2011 13:18 СлаваRa вне форума
СлаваRa
 
Аватар для СлаваRa
я использую вот такую реализацию
Код AS3:
public class SimpleCache 
	{
 
		// - STATIC PROPERTIES -------------------------------------------------------------------/
 
		// - CONSTRUCTOR -------------------------------------------------------------------------/
 
		public function SimpleCache(targetClass:Class, initialCapacity:int) 
		{
			super();
			this.init(targetClass, initialCapacity);
		}
 
		private function init(targetClass:Class, initialCapacity:int):void 
		{
			this._targetClass 	= targetClass; 					
			this._currentIndex 	= initialCapacity - 1; 			
			this._instances 	= []; 							
			for (var i:int = 0; i < initialCapacity; i++)		
				this._instances[int(i)] = this.getNewInstance();
		}
 
		// - PUBLIC PROPERTIES -------------------------------------------------------------------/
 
		// - PUBLIC METHODS ----------------------------------------------------------------------/
 
		public function get():Object
		{
			if (this._currentIndex >= 0)
			{
				this._currentIndex--;
				return this._instances[int(this._currentIndex + 1)];	
			} else {
				return this.getNewInstance(); 							
			}
		}
 
		public function set(instance:Object):void
		{
			this._currentIndex++;
			this._currentIndex == this._instances.length 					
				? this._instances[int(this._instances.length)] 	= instance  
				: this._instances[int(this._currentIndex)] 		= instance;
		}
 
		// - PRIVATE PROPERTIES ------------------------------------------------------------------/
 
		protected var _targetClass	:Class;
		protected var _currentIndex	:int;
		protected var _instances	:Array;
 
 		// - PRIVATE METHODS ---------------------------------------------------------------------/
 
		protected function getNewInstance():Object
		{
			return new this._targetClass();
		}
 
		// - EVENT HANDLERS ----------------------------------------------------------------------/
 
 		// - STATIC METHODS ----------------------------------------------------------------------/
 
		// - ACCESSORS ---------------------------------------------------------------------------/
 
	}
Старый 01.09.2011 13:26 f.g.programmer вне форума
f.g.programmer
 
Аватар для f.g.programmer
Цитата:
Получается, что конструктор работает вхолостую, а фабрика нужна для обязательных параметров конструктора
То, что объект конфигурируется после создания, кажется вполне естественным при использовании пула. В примере же с пулей, указание координаты, скорости и направления в конструкторе, кажется наоборот не очень правильным подходом.
У нас пул реализован похоже, только объкты лежащие в пуле либо должны реализовать интерфейс ICloneable с методом clone(), либо их конструктор не должен иметь параметров. Но пожалуй использование фабрики более красивое решение, хотя оно фактически требует создания фабрики для каждого пула.
Старый 01.09.2011 13:32 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
@СлаваRa
А в чем смысл приводить int к int'у?
Код AS3:
this._instances[int(i)]
@f.g.programmer
Ну да. Так там писать-то 3 строчки:
Код AS3:
package {
 
	import core.IFactory;
 
	public final class MyObjectFactory implements IFactory {
 
		public function MyObject() {
			super();
		}
 
		public function get instanceType():Class {
			return MyObject;
		}
 
		public function newInstance():Object {
			return new MyObject(param, param, param-pam-pam);
		}
 
	}
 
}
Старый 01.09.2011 13:47 GBee вне форума
GBee
 
Аватар для GBee
Цитата:
То, что объект конфигурируется после создания, кажется вполне естественным при использовании пула. В примере же с пулей, указание координаты, скорости и направления в конструкторе, кажется наоборот не очень правильным подходом.
Ну почему же, если пула, например нет, то каждый танк создаст пульку и она полетит. Пример я за уши притянул, чтобы объяснить предыдущих 3 сообщения.

Цитата:
хотя оно фактически требует создания фабрики для каждого пула
Вот мне это не по душе. Можно для классов, у которых есть параметры в конструкторе наследника сделать без параметров и использовать его.
Старый 01.09.2011 14:27 СлаваRa вне форума
СлаваRa
 
Аватар для СлаваRa
насчет int'ов, это было в какой-то статье по оптимизации, вспомню если, скину ссылку. Пишу на автомате уже.
Старый 01.09.2011 14:42 fljot вне форума
fljot
СлаваRa,
это там скорее всего было про
Код AS3:
var i:Number;
for...
{
arr[int(i)]//быстрее
}
Старый 01.09.2011 14:56 СлаваRa вне форума
СлаваRa
 
Аватар для СлаваRa
я уверен, что это так.
Старый 01.09.2011 18:42 alatar вне форума
alatar
 
Аватар для alatar
Обычно используются два варианта для универсально пула.
1. Параметры для конструктора передаются в виде массива и в фабрике прописывается мега кейс по длине массива.
2. Используется конструктор без параметров, а дополнительные параметры передаются в виде объекта.

Словарь, обычно, используется для хранения пулов (обычные массивы), ключом будет являться класс объекта. В addItem нет смысла кидать ошибку присутствия объекта, достаточно поставить возврат, если пул для этого класса существует.
Старый 02.09.2011 10:33 СлаваRa вне форума
СлаваRa
 
Аватар для СлаваRa
А есть ли смысл, при каких-либо условиях очищать (частично) пул во время работы программы?
Старый 02.09.2011 11:15 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Цитата:
В addItem нет смысла кидать ошибку присутствия объекта, достаточно поставить возврат, если пул для этого класса существует
Ассертов в AS3 нету, а проверять свой код как-то надо. Поэтому и ошибка. Я вообще стараюсь писать как можно больше ошибок, так возникает меньше непоняток
Старый 03.09.2011 17:08 murz вне форума
murz
Код AS3:
_initCount = (initCount > 0) ? initCount : 1;
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..
var len:int = _initCount - 1;
while (len--) this.addItemInternal(_factory.newInstance);
Хочу поинтересоваться что это и зачем?
Старый 03.09.2011 17:29 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Если в пуле кончились объекты, нужно создать новые. Иногда выгоднее сразу создать пачку объектов, чем создавать их по одному. Сколько за раз создать - это initCount. Ну, и понятно, что меньше 1 оно быть не может.
Старый 03.09.2011 19:00 Inet_PC вне форума
Inet_PC
 
Аватар для Inet_PC
Я бы еще добавил maxSize, что бы там не хранилось 20000 элементов
Старый 04.09.2011 00:22 murz вне форума
murz
to gloomyBrain, хитро.
Старый 10.09.2011 16:02 Azo вне форума
Azo
 
Аватар для Azo
А пример использования можно?

Например создаем экземпляр нашего какго то класса и как его запихать в пул и как его вынуть потом, и как знать что не надо второй раз создавать? Проверять перед созданием, есть в пуле или нет?
Старый 10.09.2011 19:01 СлаваRa вне форума
СлаваRa
 
Аватар для СлаваRa
2Azo
у меня, например, за создание экземпляра отвечает сам пул, т.е. мы не создаем ничего сами, мы берем из пула.
Старый 13.09.2011 23:18 dimarik вне форума
dimarik
 
Аватар для dimarik
В конце припишите, пожалуйста, к чему пришло сообщество.
Старый 19.09.2011 10:55 BornTOFree вне форума
BornTOFree
Не знаю уместным ли будет мой пост. Читал как-то официальный справочник по оптимизации кода, там так же решается подобная задача, может кто-то не знал и это поможет http://help.adobe.com/ru_RU/as3/mobi...ng_content.pdf
Страница 10
Старый 28.09.2011 14:08 BlooDHounD вне форума
BlooDHounD
 
Аватар для BlooDHounD
вот это конечно эпично:
Код AS3:
var cast:Object = value as _factory.instanceType;
if (!cast) throw new IllegalOperationError("Объект не может быть добавлен в пул из-за несоответствия типов!");
this.addItemInternal(cast);
зачем мы в пул пихаем класс? и что случилось со старым добрым is? мне кажется логичнее этот блок выглядит так:
Код AS3:
if ( !( value is _factory.instanceType ) ) {
	throw new IllegalOperationError("Объект не может быть добавлен в пул из-за несоответствия типов!");
}
this.addItemInternal( value );
Обновил(-а) BlooDHounD 28.09.2011 в 14:11
Старый 28.09.2011 17:02 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Цитата:
зачем мы в пул пихаем класс?
Я в своем коде запихивания класса в пул не вижу.
На счет is/as - согласен, если честно, даже не помню откуда там as =)
Старый 28.09.2011 18:45 incvizitor вне форума
incvizitor
 
Аватар для incvizitor
Цитата:
даже не помню откуда там as =)
Видимо, что бы получить null, в случае не соответствия типов)
Старый 28.09.2011 18:53 incvizitor вне форума
incvizitor
 
Аватар для incvizitor
И помему в IPool, addItem принимает значение IPoolable, а в классе GenericPool - Object?
Старый 28.09.2011 22:16 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Спасибо, fixed
Старый 29.09.2011 17:24 BlooDHounD вне форума
BlooDHounD
 
Аватар для BlooDHounD
Цитата:
Я в своем коде запихивания класса в пул не вижу.
ну если код исправить, то конечно видеть пихание класса перестаёшь =)
Старый 13.10.2013 21:43 Akopalipsis вне форума
Akopalipsis
gloomyBrain Спасибо Вам за познавательную статью, очень радует, что можно посмотреть реализацию на "родном языке". Но у меня к Вам вопрос - Вы часто пользуетесь им, на сколько он необходим и есть ли какие то коррективы, которые Вы узнали за прошедшее время?
Старый 14.10.2013 12:33 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
На данный момент - не часто пользуюсь. Вообще это все было написано в процессе оптимизации игрушки под iOS, потому как на мобильных устройствах любое срабатывание GarbageCollector'а может быть весьма ощутимым (проваливается FPS). При использовании пула мы имеем ситуацию, когда GC просто незачем срабатывать, так как объектов на которые нет ссылок просто не остается и, соответственно, удалять из памяти ничего не надо. Ну и опять же, экономия на создании новых объектов - их не нужно создавать, так как они уже созданы и есть в пуле.
Старый 14.10.2013 14:38 Akopalipsis вне форума
Akopalipsis
gloomyBrain Спасибо! Жду от Вас ещё каких нибудь интересных статей!
Старый 21.01.2015 13:32 Котяра вне форума
Котяра
 
Аватар для Котяра
Код AS3:
var item:CustomItem = new CustomItem();
// добавляем в пул
Pool.addItem( item);
// берём из пула объект типа CustomItem
Pool.geItem(CustomItem);
Уже в самом пуле заводим словарик, в качестве ключа - класс, в качестве значений - вектор объектов.
все классы, добавляемые в пул, должны иметь конструкторы без параметров.
Старый 06.08.2015 18:44 jony_e вне форума
jony_e
зачем удалять ячейку через pop() если можно ее просто занулить, не меняя размер массива, а потом положить в нее новое значение, что бы не вызывать каждый раз push()
Старый 26.05.2017 15:51 RAlfDog вне форума
RAlfDog
 
Аватар для RAlfDog
А зачем нужен вызов
Код:
super();
в конструкторе пула?

А ещё зачем нам нужен словарь объектов пула - метод indexOf у вектора сильно медленнее работает чем обращение к словарю? Если так беспокоиться за производительность, то нужно также избавиться и от
Код:
_pool.push(value);
. Или метода indexOf ещё не было у векторов в 11-м году?

Цитата:
Например танчики. Пусть пульки лежат в пуле. Bullet(x, y, power, angle) x,y - стартовая позиция, power - мощность заряда, angle - угол куда лететь. Танчик 1 просит у пула заряд, а там уже лежит фабричный с дефолтовыми параметрами, а не с теми, что нужны танку. Танчику 2 нужен заряд помощнее и летит в другую сторону, то есть после извлечения заряда из пула его все равно придется настраивать.
- мне кажется мы для это и делаем пул, чтобы не вызывать конструкторы каждый раз Или мы должны создавать пакетами объекты с одними и теми же начальными параметрами по запросу для Танчика 2?
Старый 26.05.2017 23:13 in4core вне форума
in4core
 
Аватар для in4core
Цитата:
А зачем нужен вызов
Код:
super();
в конструкторе пула?
Просто грамотно написано то, что компилятор и так будет делать за вас. Можно не писать, все равно будет вызов
 

 


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


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