Объектный пул: различия между версиями
Patarakin (обсуждение | вклад) |
Patarakin (обсуждение | вклад) |
||
Строка 186: | Строка 186: | ||
} | } | ||
</source> | </source> | ||
== Ссылки == | == Ссылки == |
Текущая версия на 11:29, 20 октября 2023
Объектный пул порождающий шаблон проектирования, набор инициализированных и готовых к использованию объектов. Когда системе требуется объект, он не создаётся, а берётся из пула. Когда объект больше не нужен, он не уничтожается, а возвращается в пул.
Применение
Объектный пул применяется для повышения производительности, когда создание объекта в начале работы и уничтожение его в конце приводит к большим затратам. Особенно заметно повышение производительности, когда объекты часто создаются-уничтожаются, но одновременно существует лишь небольшое их число.
Объектный пул удобен, если объект владеет другими ресурсами, кроме памяти — например, сетевыми сокетами. Либо если коллекция объектов отнимает значительную часть памяти компьютера и «мусора» создаётся действительно много.
Переполнение
Если в пуле нет ни одного свободного объекта, возможна одна из трёх стратегий:
- Расширение пула.
- Отказ в создании объекта, аварийная остановка.
- В случае многозадачной системы можно подождать, пока один из объектов не освободится.
Примеры
- Информация об открытых файлах в DOS.
- Информация о видимых объектах во многих компьютерных играх (хорошим примером является движок Doom). Эта информация актуальна только в течение одного кадра; после того, как кадр выведен, список опустошается.
- Компьютерная игра для хранения всех объектов на карте, вместо того, чтобы использовать обычные механизмы распределения памяти, может завести массив такого размера, которого заведомо хватит на все объекты, и свободные ячейки держать в виде связного списка. Такая конструкция повышает скорость, уменьшает фрагментацию памяти и снижает нагрузку на сборщик мусора (если он есть).
Пример реализации
Пример на Python
#coding: utf-8
"""
Представим ситуацию, что у нас есть корабль, который может выдержать несколько выстрелов.
Создание объекта Shot стоит дорого.
Поэтому объекты семейства Shot создаём 1 раз.
А по истечении жизни объект остаётся в памяти.
"""
class Shot(object):
"""Сущность, способная пережить несколько попаданий"""
def __init__(self, lifetime=5):
self.lifetime = lifetime
def update(self):
self.lifetime -= 1
return self.lifetime > 0
class ObjectPool:
"""Пул объектов"""
def __init__(self, **kwargs):
"""Создание пула"""
self._clsname = kwargs['classname']
self._args = kwargs.get('args', [])
self._num_objects = max(kwargs['num'], 0)
self._pred = kwargs['update_func']
self._max_objects = kwargs.get('max', self._num_objects)
# Create the objects
self._objs = [apply(self._clsname, self._args)
for x in range(self._num_objects)]
self._end = len(self._objs)
def _extend_list(self, args):
"""Добавить одно место в пул"""
self._objs.append(apply(self._clsname, args))
self._num_objects += 1
def add(self, *args):
"""Добавить один объект в пул"""
newend = self._end + 1
# Если достигнут максимум - отбой
if newend > self._max_objects:
return None
# Если заняли все места - добавляем еще одно место
if newend > len(self._objs):
self._extend_list(args)
else:
self._objs[self._end].reset(*args)
self._end += 1
return self._end - 1
def update(self, *args):
"""Обновить все объекты в пуле"""
self._end = partition(self._pred, self._objs, 0, self._end, args)
return self._end
def update_object(x):
"""Обновить объект"""
return x.update()
def partition(pred, seq, first, last, *args):
"""Функция сортировки объектов"""
if first > last:
return 0
for i in range(first, last):
if not pred(seq[i]):
break
else:
return last
for j in range(i+1, last):
if pred(seq[j]):
seq[i], seq[j] = seq[j], seq[i]
i += 1
return i
# Собственно использование пула
shots = ObjectPool(classname=Shot, update_func=update_object, num=5)
while shots.update():
pass
print "Done!"
}}
Пример на C++
#include <vector>
class Object
{
// ...
};
class ObjectPool
{
private:
struct PoolRecord
{
Object* instance;
bool in_use;
};
std::vector<PoolRecord> m_pool;
public:
Object* createNewObject()
{
for (size_t i = 0; i < m_pool.size(); ++i)
{
if (! m_pool[i].in_use)
{
m_pool[i].in_use = true; // переводим объект в список используемых
return m_pool[i].instance;
}
}
// если не нашли свободный объект, то расширяем пул
PoolRecord record;
record.instance = new Object;
record.in_use = true;
m_pool.push_back(record);
return record.instance;
}
void deleteObject(Object* object)
{
// в реальности не удаляем, а лишь помечаем, что объект свободен
for (size_t i = 0; i < m_pool.size(); ++i)
{
if (m_pool[i].instance == object)
{
m_pool[i].in_use = false;
break;
}
}
}
virtual ~ObjectPool()
{
// теперь уже "по-настоящему" удаляем объекты
for (size_t i = 0; i < m_pool.size(); ++i)
delete m_pool[i].instance;
}
};
int main()
{
ObjectPool pool;
for (size_t i = 0; i < 1000; ++i)
{
Object* object = pool.createNewObject();
// ...
pool.deleteObject(object);
}
return 0;
}
Ссылки
- Паттерн Object Pool (пул объектов) — назначение, описание, структура, особенности применения