This is the seventh article in my mini series about object pooling. Be sure to read part 1, part 2, part 3, part 4, part 5 and part 6 first. As some of you may have noticed, I introduced a bad bug in the last article about growing and shrinking, specifically in the code that would call the ExtendPoolBy and ShrinkPoolBy methods. The amount by which to grow or shrink the pool was calculated as the difference between two percentage figures and passed in to the two methods, which really expected absolute values! That’s what I get for not using test-first development on this :-) So I fixed this and introduced two simple algorithms instead to handle the calculation of the growing and shrinking amounts. This is quite an important topic for object pooling, because it defines how well the pool scales in various scenarios — sometimes it may be desirable to have a pool that adapts quickly to new requirements, sometimes it’s more important to keep the pool size stable as long as possible. In one of the next posts in the series I’m going to factor out the growing/shrinking behaviour using the strategy pattern, until then the current calculation will have to do — and the new test program shows that it doesn’t do that bad at all!

The test program

Here’s the download of the current code and the sample program. This has been created in Visual Studio 2005 beta 2. As promised, I have created a test program that uses a few threads to do work with the pool. Each thread runs a loop in which a number of objects is requested from the pool and released after a while. A random values determines if only a single object or a larger number of objects will be requested in each run. Here’s the code:

class Program : IObjectFactory<PoolableObject> {
  static void Main(string[] args) {
    new Program( ).Run( );
  }

  Pool<PoolableObject> pool;
  private const int threadCount = 3;

  void Run( ) {
    pool = new Pool<PoolableObject>(this, 0);
    pool.UseTimerBasedResizing = true;
    pool.MaxPoolSize = 100;

    Thread[] threads = new Thread[threadCount];
    for (int i = 0; i  50) {
        // boost
        int boostSize = random.Next(5, 20);
        PoolableObject[] objects = new PoolableObject[boostSize];
        for (int i = 0; i < boostSize; i++) {
          objects[i] = pool.GetObject( );
          Console.WriteLine(String.Format("Thread {0} allocated object {1}.",
            Thread.CurrentThread.ManagedThreadId, objects[i].Id));
          objects[i].DoWork( );
        }
        Thread.Sleep(random.Next(0, 5000));
        for (int i = 0; i < boostSize; i++) {
          pool.ReleaseObject(objects[i]);
          Console.WriteLine(String.Format("Thread {0} released object {1}.",
            Thread.CurrentThread.ManagedThreadId, objects[i].Id));
        }
      }
      else {
        // single
        PoolableObject poolableObject = pool.GetObject( );
        Console.WriteLine(String.Format("Thread {0} allocated object {1}.",
          Thread.CurrentThread.ManagedThreadId, poolableObject.Id));
        poolableObject.DoWork( );
        Thread.Sleep(random.Next(0, 5000));
        pool.ReleaseObject(poolableObject);
        Console.WriteLine(String.Format("Thread {0} released object {1}.",
          Thread.CurrentThread.ManagedThreadId, poolableObject.Id));
      }
    }
  }

  #region IObjectFactory<PoolableObject> Members
  PoolableObject IObjectFactory<PoolableObject>.CreateObject( ) {
      return new PoolableObject( );
  }
  #endregion
}

public class PoolableObject {
  public PoolableObject( ) {
    id = Guid.NewGuid( );
    Console.WriteLine(String.Format("Poolable object {0} is being constructed.", id));
  }

  ~PoolableObject( ) {
    Console.WriteLine(String.Format("Poolable object {0} is being destructed.", id));
  }

  private Guid id;
  public Guid Id {
    get {
      return id;
    }
  }

  int useCount;

  public void DoWork( ) {
    // Obviously this object should be able to actually do something, but
    // that's not relevant to the test program.
    // So we just count how many times an object has been used.
    Console.WriteLine(String.Format("Poolable object {0} has been used {1} times now.", id, ++useCount));
  }
}