C# 对象池






3.67/5 (9投票s)
2007年10月12日
3分钟阅读

73895

524
一篇关于勇敢者的对象池的简单文章
引言
在我被抨击之前,请允许我提到 Stoyan Damov 关于对象池的文章。尽管我的方法是独立开发的,但它确实使用了几个相同的技术。
此代码利用了WeakReference对象来执行对象池。假设是,如果 .NET 运行时在再次需要对象之前尝试回收内存,那么回收内存会更便宜。这种假设仅在设计此类时所做的假设下才有效,即这种类型的对象池对于需要大量昂贵对象且速度快的应用程序非常有效。事实上,这些对象占用的唯一资源是大量的内存,而连续的内存段的分配成本很高。虽然我无法真正看到它在商业环境中的应用,主要是因为 ASP.NET 的很大一部分都喜欢创建对象,但我可以预见到它在科学领域,尤其是图形处理和人工智能方面有许多应用。尽管我尚未能够在专业环境中使用此代码,但计时结果表明,与反复创建对象相比,速度有了显着的提高。
我不会花很多时间在此文章中详细说明代码。相反,它将作为附件包含在内。请首先阅读基本算法(如下),然后检查源代码。我认为这个特定的项目在没有直接指导的情况下,会更有趣地进行探究和拆解。
Using the Code
ObjectPool
类的基本算法如下
- 通过泛型方法
ObjectPool.GetInstance().GetObjectFromPool(null);
请求一个新对象 - 循环遍历链表以查找未使用的对象
- 将参数传递给对象的
SetupObject
方法 - 返回引用
- 在 dispose 时,添加到链表
//#define DISABLE_POOL
using System;
using System.Collections.Generic;
using System.Text;
namespace ObjectPool {
public interface IObjectPoolMethods : IDisposable {
/// <summary>
/// This method is used to setup an instance of
/// an object.
/// </summary>
/// <remarks>
/// Use an empty constructor in the class and
/// entirely rely on this method to setup and
/// initialize the object if the class is going
/// to be used for object pooling. Do not call this
/// method from the constructor or it will be called
/// twice and will affect performance.
/// </remarks>
/// <param name="setupParameters"></param>
void SetupObject(params object[] setupParameters);
/// <summary>
/// This event must be fired when dispose is called
/// or the Object Pool will not be able to work
/// </summary>
event EventHandler Disposing;
}
/// <summary>
/// A generic class that provide object pooling functionality
/// to classes that implement the IObjectPoolMethods interface
/// </summary>
/// <remarks>
/// As long as the lock(this) statements remain this class is thread
/// </remarks>
/// <typeparam name="T">
/// The type of the object that should be pooled
/// </typeparam>
public class ObjectPool<T> where T : IObjectPoolMethods, new() {
/// <summary>
/// The MAX_POOL does not restrict the maximum
/// number of objects in the object pool
/// instead it restricts the number of
/// disposed objects that the pool will maintain
/// a reference too and attempt to pool. This
/// number should be set based on a memory and
/// usage anaylsis based on the types of objects
/// being pooled. I have found the best results
/// by setting this number to average peak objects
/// in use at one time.
/// </summary>
/// <value>100</value>
private const int MAX_POOL = 2000;
/// <summary>
/// The static instance of the object pool. Thankfully the
/// use of generics eliminates the need for a hash for each
/// different pool type
/// </summary>
private static ObjectPool<T> me = new ObjectPool<T>();
/// <summary>
/// Using a member for the max pool count allows different
/// types to have a different value based on usage analysis
/// </summary>
private int mMaxPool = ObjectPool<T>.MAX_POOL;
/// <summary>
/// A Linked List of the WeakReferences to the objects in the pool
/// When the count of the list goes beyond the max pool count
/// items are removed from the end of the list. The objects at the
/// end of the list are also most likely to have already
/// been collected by the garbage collector.
/// </summary>
private LinkedList<WeakReference> objectPool =
new LinkedList<WeakReference>();
/// <summary>
/// Return the singleton instance
/// </summary>
/// <returns>
/// The single instance allowed for the given type
/// </returns>
public static ObjectPool<T> GetInstance() {
return me;
}
/// <summary>
/// This method gets called on the object disposing
/// event for objects created from this object pool class.
/// If the implementing class does not fire this event on
/// dispose the object pooling will not work
/// </summary>
/// <param name="sender">
/// The class instance that is pooled
/// </param>
/// <param name="e">
/// An empty event args parameters
/// </param>
/// <exception cref="System.ArgumentException">
/// Thrown if the type of the object in the weak reference
/// does not match the type of this generic instance
/// </exception>
private void Object_Disposing(object sender, EventArgs e) {
//The lock is required here because this method may
//be called from events on different threads
lock (this) {
Add(new WeakReference(sender));
}
}
/// <summary>
/// This method will add a new instance to be tracked by
/// this object pooling class. This should be a reference
/// to an object that was just disposed otherwise it makes no
/// sense and will likely cause some serious problems.
/// </summary>
/// <param name="weak">
/// WeakReference to the object that was disposed
/// </param>
/// <exception cref="System.ArgumentException">
/// Thrown if the type of the object in the weak reference
/// does not match the type of this generic instance
/// </exception>
private void Add(WeakReference weak) {
objectPool.AddFirst(weak);
if (objectPool.Count >= mMaxPool) {
objectPool.RemoveLast();
}
}
/// <summary>
/// Remove the reference from the pool. This should
/// only be called when the object in the pool
/// is being reactivated or if the weak reference has
/// been determined to be expired.
/// </summary>
/// <param name="weak">
/// A reference to remove
/// </param>
/// <exception cref="System.ArgumentException">
/// Thrown if the type of the object in the weak reference
/// does not match the type of this generic instance
/// </exception>
private void Remove(WeakReference weak) {
objectPool.Remove(weak);
}
///<summary>
/// This method will verify that the type of the weak
/// reference is valid for this generic instance. I haven't
/// figured out a way do it automatically yet
/// </summary>
/// <exception cref="System.ArgumentException">
/// Thrown if the type of the object in the weak reference
/// does not match the type of this generic instance
/// </exception>
private void TypeCheck(WeakReference weak) {
if (weak.Target != null &&
weak.Target.GetType() != typeof(T)) {
throw new ArgumentException
("Target type does not match pool type", "weak");
}
}
/// <summary>
/// This method will return an object reference that is fully
/// setup. It will either retrieve the object from the object
/// pool if there is a disposed object available or it will
/// create a new instance.
/// </summary>
/// <param name="setupParameters">
/// The setup parameters required for the
/// IObjectPoolMethods.SetupObject method. Need to find a
/// way to check for all of the types efficiently and at
/// compile time.
/// </param>
/// <returns>
/// The reference to the object
/// </returns>
public T GetObjectFromPool(params object[] setupParameters) {
T result = default(T);
#if DISABLE_POOL
result = new T();
result.SetupObject(setupParameters);
return result;
#else
lock (this) {
WeakReference remove = null;
foreach (WeakReference weak in objectPool) {
object o = weak.Target;
if (o != null) {
remove = weak;
result = (T)o;
GC.ReRegisterForFinalize(result);
break;
}
}
if (remove != null) {
objectPool.Remove(remove);
}
}
if (result == null) {
result = new T();
result.Disposing += new EventHandler(Object_Disposing);
}
result.SetupObject(setupParameters);
return result;
#endif
}
}
}
此代码中,链表的长度是固定的最大长度,然而,使用提供的测试应用程序使用的即时dispose
方法,我很少看到创建的对象的总数超过所使用的线程数。如果运行时已释放了列表中的一个项目,则整个列表将被截断,因为列表的顺序是根据最后一次使用时间确定的。虽然这实际上并不正确,因为 .NET 垃圾回收器的特性,但它在实际上是正确的。
我注意到这种算法/代码的一点是,随着分配的内存减少,对象池与非对象池之间的差异消失了(好吧,废话),但随着在给定时间内所需的对象数量增加,如果没有池化,应用程序的运行速度会变慢,因为操作系统开始交换。 dispose
方法似乎没有被调用。
结论
正如我在我简短的介绍中提到的,我还没有找到一个好的应用。我可能会尝试将其插入到我的内存图函数求解器算法中(有关求解器的简单实现,请参阅我的 Bridges 文章),但在那之前,它只是一个有趣的验证概念。我将对描述类似算法在高对象计数场景中使用方式的评论感兴趣(与对象计数低但资源繁重的连接池相反)。