实现原理其实很简单:根据传入的类型,获取对应的容器,在容器里进行引用的生成和归还操作。
Lock,效率很低。考虑用ThreadLocal,效率高,但要注意在某些情况下,可能会出现A线程一直请求,B线程一直回收的操作,导致内存无限上涨。之前压测的时候出过这个问题。比如在游戏里,可能20%的时间在打副本,这时候某个类型引用池一直在涨,大概峰值是5000个。但是80%的时间在空闲状态,这时候某个类型引用池峰值可能就50个。导致内存得不到效率利用,80%的时间里浪费了4500个。
可以考虑定时检查容量大小的操作,比如每隔5分钟检查引用池大小,超出阈值后就做清除/缩容处理。
这里提供一种基于历史行为的容量预测器,自适应根据引用池历史记录来进行动态扩缩的方法。大概思想是,检测一段的波峰和波谷,将容量设为合适的大小。
如果波动超过了容量,那么扩容。
如果在一段使用频率内,波动比容量的一半还要小,那么缩容。
private void CheckCapacity()
{
uint currentCount = (uint)m_References.Count;
if (currentCount < m_CurrentIntervalMinCount)
{
m_CurrentIntervalMinCount = currentCount;
uint currentIntervalNetOutflow = m_CurrentIntervalMaxCount - m_CurrentIntervalMinCount;
if (currentIntervalNetOutflow > m_RecentMaxIntervalNetOutflow)
{
m_RecentMaxIntervalNetOutflow = currentIntervalNetOutflow;
if (m_RecentMaxIntervalNetOutflow >= m_Capacity)
{
uint biggerCapacity = (uint)(m_Capacity * s_CapacityIncreaseRatio);
ResetCapacity(biggerCapacity);
}
}
}
else if (currentCount > m_CurrentIntervalMaxCount)
{
m_CurrentIntervalMinCount = currentCount;
m_CurrentIntervalMaxCount = currentCount;
}
if (++m_RecentSampleCount >= m_RecentSampleMaxCount)
{
uint smallerCapacity = (uint)(m_Capacity / s_CapacityIncreaseRatio);
if (m_RecentMaxIntervalNetOutflow < smallerCapacity)
{
ResetCapacity(smallerCapacity);
}
else
{
ResetCapacity(m_Capacity);
}
}
}
private void ResetCapacity(uint capacity)
{
if (capacity < s_OriginalCapacity)
{
capacity = s_OriginalCapacity;
}
if (m_References.Count > capacity)
{
Remove((uint)(m_References.Count - capacity));
}
m_Capacity = capacity;
m_CurrentIntervalMinCount = 0;
m_CurrentIntervalMaxCount = 0;
m_RecentMaxIntervalNetOutflow = 0;
m_RecentSampleCount = 0;
m_RecentSampleMaxCount = (uint)(m_Capacity * s_CapacityCheckPeriodRatio);
}