🎱
Прыгни с разбега в ObjectPool!Не так давно прочитал книгу ✅ Высокопроизводительный код на .Net. И теперь осознал, что память не бесконечная и тратится очень быстро с каждым вызовом new. В мученических своих поисках о том как этого всего избегать, наткнулся на прикольный класс ObjectPool.
Идея простая:1️⃣
Создаете сколько-то пустых объектов в памяти заранее
2️⃣ Потом достаете их,
заполняетете нужными данными, юзаете это дельце как не в себя
3️⃣ А в конце просто
сбрасываете состояние этого объекта к нулю и потом переиспользуете, если нужно.
Долго думал, как же эту крутотень использовать. И что-то никак не мог найти практического применения. А потом чет поскреб по сусекам и осознал, что в кэшах же!
Для примера. Есть самый простой в реализации кэш со словарем в качестве хранилища:
public class ExpensiveObject
{
public int Data { get; set; }
public ExpensiveObject()
{
Data = new Random().Next();
}
}
public class SimpleCache
{
private readonly Dictionary<string, ExpensiveObject> _cache = new Dictionary<string, ExpensiveObject>();
public ExpensiveObject Get(string key)
{
if (!_cache.TryGetValue(key, out var obj))
{
// Если объекта по ключу нет, создаём новый
obj = new ExpensiveObject();
_cache[key] = obj;
}
return obj;
}
}
В чем минус этого кэша. Каждый раз, когда вы добавляете в него новое значение, вы кладете объект в кучу. В этой реализации нет очистки кэша, и это, конечно, может плохо кончиться. Но, даже если добавить очистку, вы все равно будете через какое-то время триггерить сборку мусора, что тоже не очень хорошо.
Поэтому на первый план выходит идея – а что если заранее создать кучу пустых объектов и по очереди их заполнять нужными данными на время использования. Делаем то же самое на ObjectPool.
Сначала добавим немного сервисных штук:
using Microsoft.Extensions.ObjectPool;
public class ExpensiveObject
{
public int Data { get; set; }
public ExpensiveObject()
{
Data = new Random().Next();
}
// Добавляем метод сброса состояния для нашего класса
public void Reset()
{
Data = 0;
}
}
// Создаем политику работы с БАССЕЙНОМ для нашего объекта
public class ExpensiveObjectPooledPolicy : PooledObjectPolicy<ExpensiveObject>
{
// то бишь говорим, как создать
public override ExpensiveObject Create() => new ExpensiveObject();
// и как очистить состояние, после того как поюзали
public override bool Return(ExpensiveObject obj)
{
obj.Reset();
return true;
}
}
В этой части главное – не забыть модифицировать метод Return(ExpensiveObject obj), если добавили новое поле.
Если все сделали, то потом используем в пулле
using Microsoft.Extensions.ObjectPool;
public class OptimizedCache
{
private readonly ObjectPool<ExpensiveObject> _objectPool;
private readonly Dictionary<string, ExpensiveObject> _cache = new Dictionary<string, ExpensiveObject>();
public OptimizedCache(int maximumRetained = 20)
{
var policy = new ExpensiveObjectPooledPolicy();
_objectPool = new DefaultObjectPool<ExpensiveObject>(policy, maximumRetained);
}
public ExpensiveObject Get(string key)
{
if (!_cache.TryGetValue(key, out var obj))
{
obj = _objectPool.Get();
_cache[key] = obj;
}
return obj;
}
// Когда поюзали объект, можем его удалить.
public void Remove(string key)
{
if (_cache.TryGetValue(key, out var obj))
{
_cache.Remove(key);
_objectPool.Return(obj);
}
}
}
Если что, это непотокобезопасная реализация, разумеется, и тут разумеется не поможет конкаррент дикшенари и вот это все. То есть нужно будет чутка заморочиться с синхронизацией. Если кто-то юзает объект прямо сейчас, то нужно это отслеживать и дать чуваку доделать делишки и дождаться пока объект вернется в пул в первозданном виде.
Придется чутка подзаморочитсья. Но идею думаю вы уловили.