Click here to Skip to main content
15,867,939 members
Articles / Programming Languages / C#
Article

C# Object Pooling

Rate me:
Please Sign up or sign in to vote.
3.67/5 (9 votes)
12 Oct 20073 min read 71.7K   512   35   11
A simple article on object pooling for the brave

Introduction

Before I get blasted, let me mention Stoyan Damov's article on object pooling. Although my method was developed independently, it does use several of the same techniques.

This code makes use of the WeakReference object to perform object pooling. The assumption is made that if the .NET runtime attempts to reclaim memory before the object is needed again, it is cheaper to reclaim the memory. This assumption is only valid under the assumption I made when designing this class, i.e. this type of object pooling works well for applications that require large number of expensive objects, fast. In fact, the only resource these objects occupy is large sections of memory, of which consecutive sections are expensive to allocate. While I cannot really see an application for this in business settings, primarily because so much of ASP.NET is object creation happy, I can foresee many applications in the scientific realm, especially graphics processing and AI. Although I have not been able to use this code in a professional setting, timings indicate a significant speed improvement over just creating objects over and over.

I am not going to spend a lot of time itemizing the code in this article. Instead it will be included as an attachment. Please read the basic algorithm first (below) and then examine the source. I think this particular project will be more fun to probe and pick apart without direct guidance.

Using the Code

The basic algorithm of the ObjectPool class is as follows:

  • Request a new object via generic method ObjectPool.GetInstance().GetObjectFromPool(null);
  • Loop through the linked list to find an unused object
  • Pass parameters to the object's SetupObject method
  • Return reference
  • On dispose, add to linked list
C#
//#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
        }
    }
}

The linked list is of a fixed maximum size in this code, however, with the immediate dispose method used by the testing application provided, I rarely see a total number of objects created that exceed the number of threads used. If the runtime has disposed an item in the list, the entire list is truncated since the order of the list is done by last time used. While this isn't actually correct because of the nature of the .NET Garbage Collector, it is effectively correct.

Something that I have noticed with this algorithm/code is that as the memory allocated decreases, the difference between Object Pool and non-object pool goes away (ok, duh) but as the number of objects required in a given unit of time increases, without the pooling, the slower the application runs as the OS begins to thrash. The dispose method does not seem to be called.

Conclusion

Like I said in my brief introduction above, I haven't found a good application for this yet. I might try plugging it into my memory graph function solver algorithm (see my Bridges article for a simple implementation of the solver), but until then it was just a fun proof of concept. I would be interested in comments describing how similar algorithms have been used in a high object count scenario (as opposed to Connection Pools which have low object counts but are resource heavy).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect ERL GLOBAL, INC
United States United States
My company is ERL GLOBAL, INC. I develop Custom Programming solutions for business of all sizes. I also do Android Programming as I find it a refreshing break from the MS.

Comments and Discussions

 
GeneralMax number of objects instanciated Pin
argrithmag21-Feb-08 7:43
argrithmag21-Feb-08 7:43 
GeneralRe: Max number of objects instanciated Pin
Ennis Ray Lynch, Jr.21-Feb-08 7:50
Ennis Ray Lynch, Jr.21-Feb-08 7:50 
GeneralPooling and Transaction management in .NET Pin
cmsreddy5-Dec-07 2:28
cmsreddy5-Dec-07 2:28 
GeneralRe: Pooling and Transaction management in .NET Pin
Ennis Ray Lynch, Jr.14-Dec-07 3:16
Ennis Ray Lynch, Jr.14-Dec-07 3:16 
QuestionApplications? Pin
Dewey12-Oct-07 17:09
Dewey12-Oct-07 17:09 
AnswerI was just referring to applications Pin
Ennis Ray Lynch, Jr.12-Oct-07 18:07
Ennis Ray Lynch, Jr.12-Oct-07 18:07 
GeneralRe: I was just referring to applications Pin
Mike Doyon16-Oct-07 3:38
Mike Doyon16-Oct-07 3:38 
GeneralReferring to your sig Pin
Ennis Ray Lynch, Jr.16-Oct-07 5:04
Ennis Ray Lynch, Jr.16-Oct-07 5:04 
GeneralYour Signature! Pin
YouMiss18-Nov-07 14:09
YouMiss18-Nov-07 14:09 
Mike Doyon wrote:
Life should NOT be a journey to the grave with the intention of arriving safely in an attractive and well preserved body, but rather to skid in sideways, burger in one hand, drink in the other, body thoroughly used up, totally worn out and screaming "WOO HOO......What a ride!"


I am just here replying to your signature, no matter where you got it or perhaps you wrote yourself. I think it's brilliant.



Someone was born greatness;
Someone achieved greatness;
Someone have the greatness thrust upon him;

GeneralRe: Your Signature! Pin
Mike Doyon18-Nov-07 14:16
Mike Doyon18-Nov-07 14:16 
GeneralRe: I was just referring to applications Pin
everweb18-Nov-08 11:02
everweb18-Nov-08 11:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.