Объектный пул: различия между версиями

Материал из Поле цифровой дидактики
 
(не показаны 3 промежуточные версии этого же участника)
Строка 16: Строка 16:
# Информация о видимых объектах во многих [[компьютерная игра|компьютерных играх]] (хорошим примером является [[движок Doom]]). Эта информация актуальна только в течение одного кадра; после того, как кадр выведен, список опустошается.
# Информация о видимых объектах во многих [[компьютерная игра|компьютерных играх]] (хорошим примером является [[движок Doom]]). Эта информация актуальна только в течение одного кадра; после того, как кадр выведен, список опустошается.
# Компьютерная игра для хранения всех объектов на карте, вместо того, чтобы использовать обычные механизмы распределения памяти, может завести [[массив (программирование)|массив]] такого размера, которого заведомо хватит на все объекты, и свободные ячейки держать в виде [[связный список|связного списка]]. Такая конструкция повышает скорость, уменьшает фрагментацию памяти и снижает нагрузку на [[Сборка мусора (программирование)|сборщик мусора]] (если он есть).
# Компьютерная игра для хранения всех объектов на карте, вместо того, чтобы использовать обычные механизмы распределения памяти, может завести [[массив (программирование)|массив]] такого размера, которого заведомо хватит на все объекты, и свободные ячейки держать в виде [[связный список|связного списка]]. Такая конструкция повышает скорость, уменьшает фрагментацию памяти и снижает нагрузку на [[Сборка мусора (программирование)|сборщик мусора]] (если он есть).
== Ловушки ==
{{anchor|Объектная_клоака}}
# После того, как объект возвращён, он должен вернуться в состояние, пригодное для дальнейшего использования. Если объекты после возвращения в пул оказываются в неправильном или неопределённом состоянии, такая конструкция называется '''объектной клоакой''' ({{lang-en|object cesspool}}).
# Повторное использование объектов также может привести к утечке информации. Если в объекте есть секретные данные (например, номер [[кредитная карточка|кредитной карты]]), после освобождения объекта эту информацию надо затереть.
# Многопоточный объектный пул написать не так просто.
# На 2020-е годы в языках со сбором мусора управление памятью хорошо оптимизировано под постоянное выделение-отдачу. Так что, если объект занимает только память, руководства по Java не рекомендуют пользоваться пулами: обычный <code>new</code> требует всего десять процессорных команд. А сборщики мусора часто сканируют ссылки на объекты, а не их память — потому чем больше в памяти «живых» объектов, тем ниже производительность такого сборщика.


== Пример реализации ==
== Пример реализации ==


=== Пример на [[Python]] ===
=== Пример на [[Python]] ===
{{Hider_hiding
 
| title = Исходный текст на языке Python
| content =
<source lang='python'>  
<source lang='python'>  
#coding: utf-8
#coding: utf-8
Строка 119: Строка 110:


=== Пример на [[C++]] ===
=== Пример на [[C++]] ===
{{Hider_hiding
 
| title = Исходный текст на языке C++
| content =
<source lang="cpp">
<source lang="cpp">
#include <vector>
#include <vector>
Строка 197: Строка 186:
}
}
</source>
</source>
}}
=== Пример на [[C Sharp#|C#]] ===
{{Hider_hiding
| title = Исходный текст на языке C#
| content =
<source lang="csharp">
namespace Digital_Patterns.Creational.Object_Pool.Soft
{
    /// <summary>
    /// Интерфейс для использования шаблона "Object Pool" <see cref="Object_Pool"/>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ICreation<T>
    {
        /// <summary>
        /// Возвращает вновь созданный объект
        /// </summary>
        /// <returns></returns>
        T Create();
    }
}
</source>
}}


== Ссылки ==
== Ссылки ==

Текущая версия на 11:29, 20 октября 2023

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

Применение

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

Объектный пул удобен, если объект владеет другими ресурсами, кроме памяти — например, сетевыми сокетами. Либо если коллекция объектов отнимает значительную часть памяти компьютера и «мусора» создаётся действительно много.

Переполнение

Если в пуле нет ни одного свободного объекта, возможна одна из трёх стратегий:

  1. Расширение пула.
  2. Отказ в создании объекта, аварийная остановка.
  3. В случае многозадачной системы можно подождать, пока один из объектов не освободится.

Примеры

  1. Информация об открытых файлах в DOS.
  2. Информация о видимых объектах во многих компьютерных играх (хорошим примером является движок Doom). Эта информация актуальна только в течение одного кадра; после того, как кадр выведен, список опустошается.
  3. Компьютерная игра для хранения всех объектов на карте, вместо того, чтобы использовать обычные механизмы распределения памяти, может завести массив такого размера, которого заведомо хватит на все объекты, и свободные ячейки держать в виде связного списка. Такая конструкция повышает скорость, уменьшает фрагментацию памяти и снижает нагрузку на сборщик мусора (если он есть).

Пример реализации

Пример на 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;
}

Ссылки

Шаблон:Типы Паттернов