In this article
- Introduction
- Object what?
- Reusing objects (not classes)
- History (or about COM+ pooling)
- .NET pooling with ObjectPool
- ObjectPool library in a nutshell
- I am an IPoolableObject...
- ... so mark me with the PoolableAttribute
- ... and instantiate me via the ObjectPool
- Using the library (properly?)
- [Poolable] needs to be smart?
- Creating the pool
- Drawing objects from the pool
- Returning objects to the pool
- Destroying the pool
- Compiling ObjectPool
- TODO(s)
- Conclusion
- Reporting bugs
- Disclaimer
Introduction
This article demonstrates how useful could be the .NET custom attribute
classes and shows how to build your own pool of reusable objects, which
resembles the one in COM+. You're expected to at least have heard about
(.NET) attributed programming/custom attributes, and a bit of Managed C++
and a very little bit about reflection :) But if you don't -- don't worry!
I don't understand Java! :)
Object what?
Object pooling. If you have ever written a poolable MTS/COM+ component,
you can skip this funny intro and go directly to read
ObjectPool library in a nutshell. Otherwise keep reading...
What is a pool? A container, full with water, where fish swim. In our case,
an object pool is a container, where objects "swim" :) No, seriously,
the object pool is a container, which not only allows objects to be drawn from,
and returned back, but also creates them on the fly, whenever you want to draw
more objects than you have at hand. When you need a new object, the pool searches
for a free object to give you. If the "fish" is not found in the
pool, the pool "gives birth" to a bunch of brand new objects and
hands you one of them. If a free one was found, the pool just gives it to you.
"But why", you'll wonder, "do I need an object pool that creates objects? Can't I
just instantiate as many objects as I wish?". My answer is: Yes, you could. But
not in all cases. There are some special cases, where to just pull the caught
fish from the bucket is better (and definitely faster) then to catch a new fish.
Reusing objects (not classes)
If all objects were small and fast, the programmer would die from happiness,
that's why the world serves us heavy tasks which need heavy objects. And heavy
not only means that an object has many data embedded in properties and data
structures, but also means heavy initialization code. Imagine you have a bunch
of Dictionary objects, which are essentially the same, but are used to translate
different languages. Well, they could easily be written as a single class, which
takes an argument -- the desired language. The object then, connects to a database,
pulls N megabytes, stores it in some internal data structures and is ready to be
used. Now imagine that you should create a new object for every instance of its
client. Well, I guess you don't want to waste a minute or so for each Dictionary
creation, do you? So the problem is apparent, but the solution not yet. If there
were some mechanism which allowed you to create the objects, store them anywhere,
put them to sleep, and wake them only when you need them, you wouldn't have any
problem, right? (Maybe.:) Right! Here's were object (not class) reuse come to
help you. I've seen several implementations of object pooling, of which the best
one is Microsoft's implementation of COM+ components pooling. I'll not compete
with Microsoft (yet:), but will give the first (known to me) implementation of
object pooling for .NET objects. So read along and enjoy...
History (or about COM+ pooling)
(This subsection's text is copy/pasted from Platform SDK/Component Services/
Services Provided by COM+/Object Pooling and is copyrighted (c) material of Microsoft corp.)
Object pooling is an automatic service provided by COM+ that enables you to configure
a component to have instances of itself kept active in a pool, ready to be used by any
client that requests the component. You can administratively configure and monitor the
pool maintained for a given component, specifying characteristics such as pool size and
creation request time-out values. When the application is running, COM+ manages the pool
for you, handling the details of object activation and reuse according to the criteria
you have specified.
You can achieve very significant performance and scaling benefits by reusing objects in
this manner, particularly when they are written to take full advantage of reuse. With
object pooling, you gain the following benefits:
- You can speed object use time for each client, factoring out time-consuming
initialization and resource acquisition from the actual work that the object performs
for clients.
- You can share the cost of acquiring expensive resources across all clients.
- You can pre-allocate objects when the application starts, before any client requests
come in.
- You can govern resource use with administrative pool management�for example, by setting
an appropriate maximum pool level, you can keep open only as many database connections
as you have a license for.
- You can administratively configure pooling to take best advantage of available hardware
resources�you can easily adjust the pool configuration as available hardware resources
change.
- You can speed reactivation time for objects that use Just-in-Time (JIT) activation,
while deliberately controlling how resources are dedicated to clients.
.NET pooling with ObjectPool
Well, as I mentioned in the Reusing objects section
I'm too little to compete with Microsoft, but of the features listed above, the
ObjectPool library can (cut-down version of the Marketing blah-blah):
- speed object use time...
- share the cost of acquiring expensive resources...
- pre-allocate objects when the application starts...
- construct (configure) objects, like COM+ (not mentioned above)
ObjectPool library in a nutshell
The library is as little as cool, or as cool as little, whichever sounds
better, and in my favor:) It consists of one interface, one attribute class,
and two classes, of which one is just a helper. But before I dive deeply into
the library's implementation, I'll explain that there is no free lunch, and
free poolable objects.
Poolable objects must meet some requirements to enable a single object instance
to serve multiple clients. For example, they can't hold client state (internal
object data, persisted only while serving a particular client) or have any thread
affinity (as the object will be reused from different threads). In addition, all
objects that wish to become poolable must implement the IPoolableObject
interface, and be marked with the [PoolableAttribute]
attribute. Inheriting the
IPoolableObject
interface enables the object to perform initialization
when activated and clean up any client state on deactivation. Furthermore, object
are notified when they are created for the first (and last) time, as well as when
they are destroyed by the library and left alone with the Evil GC :). Often, it useful
to write poolable objects in a somewhat generic fashion (like the Dictionary objects I
mentioned above) so that they can be customized with some configuration state which will
make them look distinct. For example, an object might be written to hold a generic SQL
Server client connection, with a preliminary configured construction string. Well,
unlike COM+, we have more freedom here, as this is a library and not a runtime
framework, so our poolable objects could be initialized with any Object __gc*
-
derived (or boxed __value
) class instance.
I am an IPoolableObject...
Just before I begin, all interfaces, classes, etc. are defined in the
ObjectPooling
namespace, so I'll omit it in the article.
It is very straightforward to implement the IPoolableObject
(which
so much resembles IObjectControl
that I'm ashamed). Here's its
definition:
public __gc __interface IPoolableObject
{
public public:
void Construct (Object __gc* configuration);
void Create ();
void Activate ();
void Deactivate ();
void Destroy ();
};
As I said, it is very straightforward, but its tedious too. So I've written
a very simple implementation, and named it BasePoolableObject
.
The class just implements overridable stubs, for its inheritors. The only useful
thing it does alone is persisting the configuration object and exposing it as
its property. Here's its implementation:
public __gc class BasePoolableObject : public IPoolableObject
{
protected protected:
BasePoolableObject () :
m_configuration (0)
{
}
public public:
void Construct (Object __gc* configuration)
{
this->m_configuration = configuration;
}
virtual void Create ()
{
}
virtual void Activate ()
{
}
virtual void Deactivate ()
{
}
virtual void Destroy ()
{
}
__property Object __gc* get_Configuration ()
{
return (m_configuration);
}
public protected:
Object __gc* m_configuration;
};
An (almost) poolable object could now be created with no bigger effort than
typing the following line of code:
public __gc class SampleObject : public BasePoolableObject {}
... so mark me with the PoolableAttribute
But I said almost. You have to do just one more thing. Apply to
the poolable class the PoolableAttribute
attribute, or in C#
-- add [Poolable]
on top of your class' declaration. However,
you can control some interesting aspects of the pooling, so I'll give you
some source code of the PoolableAttribute
class. But even
before I show you the code, you'll need to know, that the attribute class
itself is marked with a .NET custom attribute -- the AttributeUsageAttribute
one. It is needed to mark our Poolable
as applicable only to
classes, and mark it for single use.
[AttributeUsageAttribute (
AttributeTargets::Class,
AllowMultiple = false)]
Now, as I'm writing this article, I'm dreaming of the time I'll have some
more free time to write for fun, I thought of having multiple ways to pool
objects in the object pool, so don't worry when you see the PoolingType
property of the attribute. It's here -- but its not take into consideration (yet).
[FlagsAttribute]
public __sealed __value enum PoolingType : int
{
NormalPooling = 0x0000001,
WeakReferences = 0x0000002,
WeakPooling = NormalPooling | WeakReferences,
Runtime = 0x0000004
};
[AttributeUsageAttribute (
AttributeTargets::Class,
AllowMultiple = false)]
public __gc class PoolableAttribute : public Attribute
{
public public:
PoolableAttribute (
PoolingType poolingType,
bool constructable,
int minPoolSize,
int preferedPoolSize,
int maxPoolSize,
int creationTimeout) :
m_poolingType (poolingType),
m_constructable (constructable),
m_minPoolSize (minPoolSize),
m_preferedPoolSize (preferedPoolSize),
m_maxPoolSize (maxPoolSize),
m_creationTimeout (creationTimeout)
{
CheckState ();
}
__property PoolingType get_Pooling ()
{
return (m_poolingType);
}
__property void set_Pooling (PoolingType poolingType)
{
m_poolingType = poolingType;
CheckState ();
}
private private:
void CheckState ()
{
if (m_minPoolSize > m_preferedPoolSize ||
m_minPoolSize > m_maxPoolSize ||
m_preferedPoolSize > m_maxPoolSize)
throw (new ArgumentException (
S"The condition min <= prefered <= " +
S"max pool size was not met");
if (m_creationTimeout < -1)
m_creationTimeout = InfiniteCreationTimeout;
}
PoolingType m_poolingType;
bool m_constructable;
int m_minPoolSize;
int m_preferedPoolSize;
int m_maxPoolSize;
int m_creationTimeout;
static const bool DefaultConstructable = false;
static const PoolingType DefaultPoolingType =
PoolingType::NormalPooling;
static const int DefaultMinPoolSize = 1;
static const int DefaultPreferedPoolSize = 16;
static const int DefaultMaxPoolSize = 100000;
static const long int DefaultCreationTimeout = 60000;
public public:
static const long int MaxCreationTimeout =
0x80000000;
static const long int InfiniteCreationTimeout = -1;
};
... and instantiate me via the ObjectPool
Now, when we're done talking, I'll show you a real poolable class
in a couple of lines. It's not smart, it's not doing anything cool,
it's just a poolable class I actually use in the sample (in the source
code). Here it is:
[PoolableAttribute (
PoolingType::NormalPooling,
true,
5,
16,
18)]
public __gc class SampleObject : public BasePoolableObject
{
public public:
void Create ()
{
}
void DoWork ()
{
Console::WriteLine (S"...");
}
};
Yours could be a two-liner, e.g.
[PoolableAttribute(PoolingType::NormalPooling, true, 5, 16, 18)]
public __gc class SampleObject : public BasePoolableObject {}
Now you've seen how to create a poolable class, but you haven't
seen how to use it, right? Now is the time! All object creation requests
are handled through the ObjectPool
class. It maintains
instances of the poolable class in a pool, ready to be activated for
any client requesting the object, just like the COM+ object manager.
And because, I'm better in writing code, than "talking" look
how easy it is to create an object pool:
ObjectPool __gc* pool = new ObjectPool(__typeof(SampleObject));
But you haven't created the pool already:) I intentionally provided
a method for creating and destroying an object pool to avoid the overhead
if you're planning to use it but it is possible that you won't. So the
pool is not actually created in the constructor, but could be very easily
created like in the code below:
pool->CreatePool (S"Hello World!");
ObjectPool __gc* pool = (new ObjectPool ())->CreatePool(S"...");
By the way, the pool can work in two modes: "exhaustible" and
"non-exhaustible". In the former mode, when there are no more
free objects in the pool, but a request to draw one is executed, the pool
throws an exception. In the non-exhaustible mode, the pool blocks, waiting
for a free object to be returned, and returns it to the client. If one
properly uses the pool (i.e. draws objects only when she needs them, and
returns them when she's finished using them), she wouldn't need the pool
to work in non-exhaustible mode (?). However, under a very heavy load,
the pool may really get exhausted, and throwing exceptions is not a very
cool solution, so I made the pool working in one of the aforementioned modes.
Here are some examples of creating both kinds of pools:
ObjectPool __gc* CreatePool (Object __gc* configuration,
bool canExhaust);
pool->CreatePool ();
pool->CreatePool (false);
pool->CreatePool (new Stack ());
pool->CreatePool (false, S"Hello");
Now, when we have the object pool up and ready, we're ready in our turn
to go "fish". The only thing, that is easier than the pooled
object instantiation is the normal instantiation via the new
operator:) But here's how you'd instantiate an object via the pool:
SampleObject __gc* obj = pool->Draw ();
I'm lying. It's not that easy, as the objects are created at runtime
via a mechanism, known as reflection. Look at the method below to see why:
IPoolableObject __gc* CreatePoolableObject ()
{
IPoolableObject __gc* po =
static_cast<IPoolableObject __gc*> (
Activator::CreateInstance (m_type));
if (m_constructable)
po->Construct (m_configuration);
po->Create ();
return (po);
}
Because the CreateInstance
method of the Activator
class returns Object
, the only thing I could do better is to
cast it to IPoolableObject
, as I know that only these kinds of
objects are present in the pool. That's all I can do. Well, to convert it to
SampleObject
, we need at least (and enough) a static_cast
,
like in the example below:
SampleObject __gc* obj = static_cast<SampleObject __gc*> (pool->Draw ());
Now, it's time to see the ObjectPool
class, isn't it? OK, but before your rush,
I'm warning you that I deleted the "uninteresting" code from the listing
below, so take a look at the "ObjectPool.h" file for details:
ObjectPool (Type __gc* type)
: m_type (type),
m_canExhaust (true),
m_created (false),
m_pool (0),
m_freeObjects (0),
m_canPoolType (false)
{
if (m_type->IsSubclassOf (__typeof (IPoolableObject)))
throw (new ArgumentException (
S"The type should derive from IPoolableObject",
S"type"));
m_sync = new SyncGuard (this);
ObtainTypeInformation ();
}
ObjectPool __gc* CreatePool (
Object __gc* configuration,
bool canExhaust)
{
Trace (S"CreatePool: Creating pool");
if (m_created)
throw (new InvalidOperationException(
S"Pool has been already created"));
m_sync->Lock ();
m_canExhaust = canExhaust;
m_configuration = configuration;
m_pool = new Hashtable (m_preferedSize);
m_freeObjects = new Stack (m_preferedSize);
m_created = true;
m_sync->Unlock ();
Grow (m_minSize);
return (this);
}
void DestroyPool ()
{
Trace (S"DestroyPool: Destroying pool");
if (! m_created)
throw (new InvalidOperationException (
S"Pool cannot be destroyed, becuase " +
S"it has not been created"));
m_sync->Lock ();
IDictionaryEnumerator __gc* dict = m_pool->GetEnumerator ();
while (dict->MoveNext ())
{
IPoolableObject __gc* po =
static_cast<IPoolableObject __gc*> (dict->Value);
po->Deactivate ();
po->Destroy ();
}
m_pool->Clear ();
m_freeObjects->Clear ();
m_created = false;
m_sync->Unlock ();
}
IPoolableObject __gc* Draw ()
{
Trace (S"Draw: Drawing object");
if (! m_created)
throw (new InvalidOperationException (
S"Pool has not been created"));
if (! m_canPoolType)
{
Trace (S"Draw: Type not poolable, creating it");
return (CreatePoolableObject ());
}
IPoolableObject __gc* po = InternalDraw ();
if (po != 0)
{
Trace (S"Draw: Object activated and used from the pool");
return (po);
}
else
{
Trace (S"Draw: No free objects, trying to grow");
if (Grow (-1))
{
return (InternalDraw ());
}
else
{
if (m_canExhaust)
{
throw (new InvalidOperationException (
S"Pool exhausted!"));
}
else
{
Trace (S"Draw: Waiting for free object " +
S"to return in the pool");
while (po == 0)
{
po = InternalDraw ();
Thread::Sleep (0);
}
Trace (S"Wait finished. At least " +
S"one free object returned.");
return (po);
}
}
}
}
void Return (IPoolableObject __gc* object)
{
Trace (S"Return: Returning object in the pool");
if (! m_created)
throw (new InvalidOperationException (
S"Pool has not been created"));
if (! m_pool->ContainsKey (object))
throw (new InvalidOperationException (
S"Object was not created by this object pool"));
if (! m_canPoolType)
{
Trace (S"Return: Object not poolable, " +
S"leaving GC do its work");
return;
}
object->Deactivate ();
m_sync->Lock ();
m_freeObjects->Push (object);
Trace (S"Return: Object deactivated and " +
S"returned in the pool");
m_sync->Unlock ();
}
void ObtainTypeInformation ()
{
Trace (S"ObtainTypeInformation: Obtaining type information");
m_sync->Lock ();
Object __gc* attArray __gc[] =
m_type->GetCustomAttributes (true);
Attribute __gc* atts __gc[] =
static_cast<Attribute __gc* __gc[]> (attArray);
for (int i=0; i<atts->Length; i++)
{
if(atts [i]->GetType () == __typeof(PoolableAttribute))
{
m_canPoolType = true;
PoolableAttribute __gc* pt =
static_cast<PoolableAttribute __gc*> (atts [i]);
m_constructable = pt->Constructable;
m_poolingType = pt->Pooling;
m_minSize = pt->MinPoolSize;
m_preferedSize = pt->PreferedPoolSize;
m_maxSize = pt->MaxPoolSize;
m_creationTimeout = pt->CreationTimeout;
break;
}
}
m_sync->Unlock ();
}
IPoolableObject __gc* InternalDraw ()
{
m_sync->Lock ();
if (m_freeObjects->Count > 0)
{
IPoolableObject __gc* po =
static_cast<IPoolableObject __gc*>(
m_freeObjects->Pop ());
m_sync->Unlock ();
po->Activate ();
Trace(S"InternalDraw: Free object found and activated");
return (po);
}
m_sync->Unlock ();
Trace (S"InternalDraw: Could not find a free object");
return (0);
}
bool Grow (int objectCount)
{
Trace (S"Grow: Attempting to grow");
m_sync->Lock ();
if (objectCount == -1)
objectCount = Math::Min (m_maxSize - m_pool->Count,
DefaultGrowSize);
if (objectCount <= 0)
{
Trace (S"Grow: Pool exhausted. Can't grow pool");
m_sync->Unlock ();
return (false);
}
Trace (S"Grow: Creating and constructing {0} objects",
__box (objectCount));
for (int i=0; i<objectCount; i++)
{
IPoolableObject __gc* po = CreatePoolableObject ();
m_pool->Add (po, po);
m_freeObjects->Push (po);
}
m_sync->Unlock ();
Trace (S"Grow: Grown successfully");
return (true);
}
Type __gc* m_type;
bool m_canExhaust;
SyncGuard __gc* m_sync;
bool m_created;
Hashtable __gc* m_pool;
Stack __gc* m_freeObjects;
bool m_canPoolType;
Object __gc* m_configuration;
PoolingType m_poolingType;
bool m_constructable;
int m_minSize;
int m_maxSize;
int m_preferedSize;
int m_creationTimeout;
static int DefaultGrowSize = 16;
I don't think that the synchronization will be interesting to anyone, so I'll
say only that it uses the Monitor
class from the
System::Threading
namespace. You can peek at the code and see how
simple it is. Initially I thought I should use the ReaderWriterLock
class, but after a little thought (and a bang on the head by a friend:) I dropped
it and stuck to the sufficient Monitor
class.
Using the library (properly?)
Well, I guess you've used it already, reading the topics above. If you haven't
read them and jumped right here, I'd suggest that you go back and read them. However,
I'd like to say a couple of words about the (proper) use of the library. So read
ahead...
[Poolable] needs to be smart?
.NET attribute classes are not dynamic, so they can't be smart. I mean that
before you place that [Poolable]
attribute you should think how
many instances are likely to be used initially, on the average and their maximum.
Once you apply the attribute to the class, you'll have to recompile it when
you change it, ok? So you'll have to carefully choose the values for
minPoolSize
, preferedPoolSize
and maxPoolSize
,
as well the timeout for the (initial) object creation. If you don't want to set
a creation timeout, simply pass InfiniteCreationTimeout
. By default,
all objects are attempted to be created in 60 seconds. If the creation fails
because the object did not respond in a timely manner, the object is discarded
as if it were never created. But relax :), I'll implement the object creation
timeout next week, because I'm very busy right now. So you can expect that your
objects will be created no matter how much time they waste...
Creating the pool
Object pools are created per type, e.g. class. Once you create an object
pool of type SomeType
, you cannot draw object of
AnotherType
, or return AnotherType
object in the
SomeType
pool. Sounds restrictive, but that's the proper way.
The other way is a way to the programming hell, believe me. This is not
your old (?) COM+, and I'm not Microsoft (though I'd like to work there
one day :) As you can see from the ObjectPool
source above,
when the ObjectPool
class is instantiated, no pool is really
created, until you call the Create
method.
ObjectPool __gc* pool =
new ObjectPool (__typeof (SampleObject));
Hashtable __gc* objectState = new Hashtable ();
objectState->Add (S"key1", s"Value1");
pool->CreatePool (objectState);
Drawing objects from the pool
Before you write the code to draw an item from the pool, write the
code for returning it to the pool. If you forget to return the object
to the pool, nothing bad will happen to Windows, the CLR and probably
your application. What WILL happen is that you'll loose the object.
The object pool keeps track of the objects, so at its destructor
it properly deactivates and destroys ALL objects, no matter if they
were returned to the pool or not. But the object will not be in the
"free list", so it will be unusable. Again, be sure to specify
a good initial number of objects that need to be created to avoid the
penalty of creating objects at critical times during the runtime of
your application. (Also, you can change the DefaultGrowSize
constant in the source code of the ObjectPool
class or
even expose it as the PoolableAttribute
's property. If you
forgot how an object is drawn from the pool, here's the code:
SampleObject __gc* obj =
static_cast<SampleObject __gc*> (pool->Draw ());
Returning objects to the pool
Please, return them :) Don't let them get lost, as later, the Evil GC will
find them, and then who knows? :) I'll provide the "return object" construct
only because I know most of you have skipped (some of) the previous sections.
pool->Return (obj);
Destroying the pool
There's no problem if you forget to destroy the pool. When the GC gets it later,
its destructor will deactivate and destroy all pooled objects. However, if your
poolable class uses unmanaged resources, and releases them in its Destroy
or Deactivate
methods, it would be nice to destroy the pool. The code
to do that is just not worth to type.
Compiling ObjectPool
There is Src
folder, containing 4 other folders:
Bin
, ObjectPooling
, ObjectPoolingTest
and CSharpPoolingTest
. When you build the solution file, all
executables and assemblies will go into the Bin
folder. The
ObjectPooling
folder contains the library's source files, the
ObjectPoolingTest
folder contains an example of ObjectPool library
client, and the CSharpPoolingTest
-- guess what:)
Now, I won't excuse for not building a command-line batch file for building the
library and the sample application. It's fairly easy to write your own, as the
library only uses mscorlib.dll
and System.dll
. If you
have VS.NET or VC++ .NET, just build the solution. I'm a very busy boy, so I
never have the time to write such batch files, sorry!
TODO(s)
There are two things I haven't done (or done partially):
- Object creation timeout is not inspected and creation is not monitored
or done asynchronously, though a simple thread, pulsing an event would do
a very good job, but... next week I guess.
- The pooling with the
WeakReference
is not implemented, as
I'm just figuring out how to do it:) The idea, by the way, is Jeffery Richter's
so I'm giving him his credit right here and right now -- Thanks Jeff!
Conclusion
Thanks for reading the article! I was expecting that some people would
peek at it, and I'm glad the YOU have actually read the whole of it, thanks
again!
Object pooling is a very old programming paradigm, but I remember since
MTS that very few programmers take advantage of it. Furthermore, pooling
COM+ components was not very recommended for VB COM components, so only the
C++ guys could make their objects poolable, fast, free-threaded, etc. (And
I was a VB programmer once :) Now, you can pool your .NET objects not only with the
runtime provided for COM+ components written in .NET languages, but for
every simple object that deserves such a special attention. Of course, I'm
sure there are many guys out there that could laugh at the implementation,
suggest super-duper data structures beside the hash table and the stack
I've used. I'll tell one thing -- CodeProject is to teach and learn, not to
show up how COOL you (may think you) are, and the .NET Hashtable
and Stack
classes are present in every .NET-enabled :) machine.
Reporting bugs
I don't know any (as I just wrote the article:), so if you find one, please,
let me know. My e-mail address is at the bottom of the article (and an alternative
one is stoyan_damov[at]hotmail.com, but PLEASE!!! do not spam me. Thanks!
Disclaimer
The software comes �AS IS�, with all faults and with no warranties.
If you find the source code useful thank me in your mind :)