Archives for: September 2005

Pages: 1 2 3 4 5 >>

25/09/05

Permalink 03:31:19 pm
Categories: General, Programming, .NET

Object pooling, part 7 - the first test program

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 < threadCount; i++) {
        threads[i] = new Thread(new ThreadStart(ThreadMethod));
        threads[i].Start( );
      }

      while (true) {
        Console.WriteLine("Current pool size: " + pool.Size);
        Thread.Sleep(2000);
      }
    }

    void ThreadMethod( ) {
      Random random = new Random( );
      while (true) {
        if (random.Next(0, 100) > 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));
    }
  }

22/09/05

Permalink 10:16:49 am
Categories: General, Programming, .NET

Object pooling, part 6 - growing and shrinking

This is the sixth article in my mini series about object pooling. Be sure to read part 1, part 2, part 3, part 4 and part 5 first.

It took me a while to find the time for the next article, but here it is. Now we're finally going to deal with the topic of growing and shrinking the pool.

One of the first questions we have to ask when it comes to this is, when are we going to do it? We have one mechanism for growing the pool implemented already, which takes place when, during a call to the GetObject method, no unused object can be found in the pool. But is this the right place to think about growing, really? And what about shrinking?

I think it's a good idea to keep these things out of the way of "normal call flow". Meaning, if possible, a client's call to get an object from the pool shouldn't be delayed by management work. So I have implemented the mechanism with the help of a timer: a regular check is executed to find whether the number of used objects in the pool is too high or too low and growing or shrinking is done accordingly.

To classify the number of used objects as "too high" or "too low", I've introduced two properties "HighWaterMark" and "LowWaterMark". These are percentage values: if the percentage of used objects in the pool is higher than HighWaterMark, the pool is considered too small and vice versa. To prevent the pool size from being scaled up and down wildly in certain scenarios, I have also introduced threshold values - so the percentage has to be found to be too high at more than one check in a row, for example, for the pool to be grown. Actually, the threshold is more useful in the shrinking than the growing case, but it's the same principle.

So, here's the code for these changes. The next article will introduce a sample program to test all the functionality that's implemented so far and I'll post the source code for the pool and the sample with it. So if you haven't been typing in all this code yourself, stay tuned for the next installment in the series!

class Pool<T> {
  . . .

  private Pool( ) {
    pool = new List<Slot>( );
    retryWaitTime = 500;
    poolExtensionBatchSize = 10;
    poolExtensionBatchSizeIsPercent = true;
    maxGetObjectTries = 5;
    outOfObjectsBehaviour = OutOfObjectsBehaviour.ExtendPool;
    delayObjectCreation = false;
    maxPoolSize = 100;
    lowWaterMark = 20;
    highWaterMark = 80;
    resizingWaitTime = 2000;
    growThreshold = 0;
    shrinkThreshold = 3;
  }

  Timer resizingTimer;
  object resizingTimerLock = new object( );

  private bool useTimerBasedResizing;
  /// 
  /// Gets or sets a value indicating whether the pool checks for low and high
  /// water situations automatically and regularly. Also see ResizingWaitTime.
  /// 
  public bool UseTimerBasedResizing {
    get {
      return useTimerBasedResizing;
    }
    set {
      if (useTimerBasedResizing != value) {
        useTimerBasedResizing = value;
        lock (resizingTimerLock) {
          if (useTimerBasedResizing == false && resizingTimer != null)
            resizingTimer.Dispose( );
          else if (useTimerBasedResizing == true)
            resizingTimer = new Timer(new TimerCallback(ResizingTimerCallback), 
              null, resizingWaitTime, Timeout.Infinite);
        }
      }
    }
  }

  private int resizingWaitTime;
  /// 
  /// Gets or sets a value that indicates the time between to checks for low and 
  /// high water situations. Also see UseTimerBasedResizing.
  /// 
  public int ResizingWaitTime {
    get {
      return resizingWaitTime;
    }
    set {
      if (resizingWaitTime != value) {
        resizingWaitTime = value;
        lock (resizingTimerLock) {
          if (resizingTimer != null)
            resizingTimer.Change(resizingWaitTime, Timeout.Infinite);
        }
      }
    }
  }

  private int highWaterMark;
  /// 
  /// Gets or sets a value indicating the percentage of used objects in the pool
  /// that must be exceeded for the pool to be grown automatically.
  /// 
  public int HighWaterMark {
    get {
      return highWaterMark;
    }
    set {
      if (highWaterMark != value) {
        highWaterMark = value;
      }
    }
  }

  private int lowWaterMark;
  /// 
  /// Gets or sets a value that the percentage of used objects in the pool
  /// must fall below for the pool to be shrunken automatically.
  /// 
  public int LowWaterMark {
    get {
      return lowWaterMark;
    }
    set {
      if (lowWaterMark != value) {
        lowWaterMark = value;
      }
    }
  }

  private int shrinkThreshold;
  /// 
  /// Gets or sets a value indicating the number of times the pool must 
  /// be judged too big before it is shrunken.
  /// 
  public int ShrinkThreshold {
    get {
      return shrinkThreshold;
    }
    set {
      if (shrinkThreshold != value) {
        shrinkThreshold = value;
      }
    }
  }

  private int growThreshold;
  /// 
  /// Gets or sets a value indicating the number of times the pool must 
  /// be judged too small before it is grown.
  /// 
  public int GrowThreshold {
    get {
      return growThreshold;
    }
    set {
      if (growThreshold != value) {
        growThreshold = value;
      }
    }
  }

  Slot FindUnusedSlot( ) {
    return pool.Find(delegate(Slot slot) {
      return !slot.InUse;
    });
  }

  /// 
  /// Shrinks the pool size by count elements, at most. The method removes
  /// only unused pool objects, as long as it finds them.
  /// 
  public void ShrinkPoolBy(int count) {
    if (count < = 0)
      throw new ArgumentOutOfRangeException("Count must be greater than zero.");
    lock (poolLock) {
      count = Math.Max(pool.Count - count, 0);
			
      Slot unusedSlot;
      do {
        unusedSlot = FindUnusedSlot( );
        if (unusedSlot != null)
          pool.Remove(unusedSlot);
        count--;
      } while (count > 0 && unusedSlot != null);
    }			
  }

  /// 
  /// Returns the number of used objects in the pool.
  /// 
  public int GetInUseCount( ) {
    int count = 0;
    pool.ForEach(delegate(Slot slot) {
      if (slot.InUse)
      count++;
    });
    return count;
  }

  /// 
  /// Returns the percentage of used objects in the pool.
  /// 
  public int GetFillLevel( ) {
    return GetInUseCount( ) / pool.Count * 100;
  }

  int fillLevelFoundTooLow;
  int fillLevelFoundTooHigh;

  void ResizingTimerCallback(object state) {
    int fillLevel = GetFillLevel( );
			
    if (fillLevel < lowWaterMark) {
      fillLevelFoundTooLow++;
      fillLevelFoundTooHigh = 0;
    }
    else if (fillLevel > highWaterMark) {
      fillLevelFoundTooHigh++;
      fillLevelFoundTooLow = 0;
    }
    else
      fillLevelFoundTooHigh = fillLevelFoundTooLow = 0;

    if (fillLevelFoundTooLow > shrinkThreshold) {
      ShrinkPoolBy(lowWaterMark - fillLevel);
      fillLevelFoundTooLow = 0;
    }
    else if (fillLevelFoundTooHigh > growThreshold) {
      ExtendPoolBy(fillLevel - highWaterMark);
      fillLevelFoundTooHigh = 0;
    }

    lock (resizingTimerLock)
      if (resizingTimer != null)
        resizingTimer.Change(resizingWaitTime, Timeout.Infinite);
  }

  . . .
}

14/09/05

Permalink 04:41:15 pm
Categories: General, Programming, .NET

LINQ resources

Just to pass on another piece of news from PDC, here's a list of information resources about LINQ, which is a fantastic extension to common programming models that we have today. Problem is, it seems to be quite a while away :-)

Permalink 04:38:02 pm
Categories: General

New Office 12 gui

Among the news coming from PDC are reports from the first public presentation of Office 12. Lots of gui changes is the most obvious thing to know about that. They replaced the menu bar with the ribbon... hm :-) Go have a look!

Permalink 10:32:05 am
Categories: General

Project challenge expo in London

It's next week and I'm going to be there. Registration is free, so if you're at all interested in project management or anything that has remotely to do with it, be sure to check out the website and consider coming!

1 2 3 4 5 >>

Enter your email address:

Search

Oliver
MVP logo
September 2005
Sun Mon Tue Wed Thu Fri Sat
 << < Current> >>
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30