Design patterns in the test of timeFlyweight
A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory.
On the face of it, this patterns looks very much like something from the old ages. And indeed, most implementations of Flyweight are actually focused deeply on low memory conditions. I would actually argue that you need to consider very carefully when you want to do that.
That said, it is actually used fairly often in high performance places. In the .NET framework, the notion of string interning is one way to get flywieghts (although the problem is that you need to start with a string to get the intern string sort of mess things up). In both the profilers and in RavenDB, we have used variations on the Flyweight pattern.
In the profiler, we are mostly dealing with parsing data from the profiled system, and that means doing a lot of reading from a stream and creating objects. That created an unacceptable memory pressure on the system. We implemented a fairly complex system where we can read from the stream into a buffer, then get or create the string from it. We contributed the implementation back to the Protocol Buffers project. You can see the code here.
In RavenDB, we deal a lot with documents, and many times we find it useful to be caching a document. The problem with doing that is that you need to return something from the cache, which means that you have to return something mutable. Instead of copying all of the data all the time, the internal RavenDB data structures supports copy-on-write semantics, which means that we can easily create clones at basically no cost.
Recommendation: If you are in a perf optimization mode, and you worry about memory pressure, consider using this. Otherwise, like all optimizations, it should be left alone until you have profiler results that says you should consider this.
More posts in "Design patterns in the test of time" series:
- (21 Jan 2013) Mediator
- (18 Jan 2013) Iterator
- (17 Jan 2013) Interpreter
- (21 Nov 2012) Command, Redux
- (19 Nov 2012) Command
- (16 Nov 2012) Chain of responsibility
- (15 Nov 2012) Proxy
- (14 Nov 2012) Flyweight
- (09 Nov 2012) Façade
- (07 Nov 2012) Decorator
- (05 Nov 2012) Composite
- (02 Nov 2012) Bridge
- (01 Nov 2012) Adapter
- (31 Oct 2012) Singleton
- (29 Oct 2012) Prototype
- (26 Oct 2012) Factory Method
- (25 Oct 2012) Builder
- (24 Oct 2012) A modern alternative to Abstract Factory–filtered dependencies
- (23 Oct 2012) Abstract Factory
Another example of flyweight is the Singleton lifestyle that many DI Containers (e.g. Castle Windsor) offer.
@Mark, no singleton is just that a singleton, flyweight is something else entirely. It's also comparing apples and oranges. IoC Containers are an entire framework that utilizes multiple patterns. the inner workings of an IoC may utilize flyweight, but that is agnostic about how the IoC is consumed by the application itself. From the calling codes point of view an IoC container acts as a registry or factory.
I used this for a text-mining project, after exhausting first the 32-bits heap, then (after targeting 64-bits mode) the virtual memory. It really sped up the calculations by at least 10-fold (by not trashing items to swap).
The thing about the Flyweight pattern is that it's pretty easy to understand, has a low cost to implementing it, doesn't tend to suffer much abuse, and usually gives significant benefits in terms of memory use in particular. I could also foresee some situations where it could be quite tricky to retro-fit it at a later date if necessary. With that in mind, I'd have thought that it would be good to keep it in mind as a possibility when first writing your code, rather than waiting till your profiler demands it.
I personally think the oft-quoted dictum, "Premature optimisation is the root of all evil" should be taken with a pinch of salt. A failure to optimise your code is actually a form of technical debt, and an optimisation is only premature if the size of that debt is small compared to the cost of implementing it, or compared to other performance bottlenecks elsewhere. I'm not entirely convinced that this is generally the case in situations where a Flyweight is up for consideration.
@Jason Meckley, notice that I wrote "Singleton lifestyle" - not "Singleton pattern". It's not the same thing.