This project is an implementation of a function-level/fine-grained cache (a.k.a. memoizer). It is based on an implementation from the book "Java Concurrency in Practice" by Brian Goetz et. al. - ported to C# 4.0. The noble thing about this implementation is that the values are not cached, but rather asynchronous tasks for retrieving those values. These tasks are guaranteed not to be executed more than once in case of concurrent first-time invocations.
A System.Runtime.Caching.MemoryCache
instance is used as cache, enabling configuration via the System.Runtime.Caching.CacheItemPolicy
. Default cache configuration is: items to be held as long as the CLR is alive (or until the item is explicitly removed).
Memoizer.NET adds a set of extension methods to your Func
references:
.CachedInvoke([args]*)
.Memoize()
.CacheFor([expiration value]1)
.RemoveFromCache([args]*)
.UnMemoize()
The first one, CachedInvoke
, memoizes the given argument combination, using the default cache configuration. (It is named mimicking the regular Func
methods Invoke()
and DynamicInvoke()
.)
The second method, Memoize
, is the one that gets you into "memoizer config mode".
The third method, CacheFor
, is a shortcut for Memoize
and gets you straight into cache expiration configuration.
The two last extension methods deals with explicit/forced expiration, see below.
Func<long, string> myExpensiveFunction = ...
string val = myExpensiveFunction.CachedInvoke(someId);
string val = myExpensiveFunction.Memoize().KeepItemsCachedFor(30).Minutes.GetMemoizer().InvokeWith(someId);
Or:
string val = myExpensiveFunction.CacheFor(30).Minutes.GetMemoizer().InvokeWith(someId);
myExpensiveFunction.RemoveFromCache(someId);
myExpensiveFunction.UnMemoize();
The "inlined" style, where the memoizer is configured and created/retrieved multiple times at runtime, works - because the memoized method handles are themselves memoized (behind the curtain), using a memoizer registry. More on that below.
Memoizer.NET does not support memoization of recursive functions out of the box, as it does not do any kind of IL manipulation.
E.g the ubiquitous Fibonacci sequence example will not work just by memoizing the root function. Instead, the recursion points have to be memoized, like this:
Func<int, long> fibonacci = (arg =>
{
if (arg <= 1) return arg;
return fibonacci.CachedInvoke(arg - 1) + fibonacci.CachedInvoke(arg - 2);
});
Now, the fibonacci
function can be invoked as a regular C# function, but orders of magnitude faster.
Obtaining the IMemoizer
object:
IMemoizer memoizedFunc = myExpensiveFunction.GetMemoizer():
Or with an expiration policy:
IMemoizer memoizedFunc = myExpensiveFunction.CacheFor(30).Minutes.GetMemoizer();
The memoizer registry is shared memoization of these IMemoizer
objects using the Memoizer.NET itself. A combined hash consisting of the Func
reference and the expiration policy, is used as key. This means that the same Func
reference with different expiration policies are treated as two different IMemoizer
instances by the memoizer registry. The GetMemoizer
method consults the memoizer registry when creating IMemoizer
instances.
By working directly against an IMemoizer
instance, you get a performance benefit. This is because only one cache invocation is needed instead of two, as being the case when working directly with Func
references. (One for retrieving the memoizer from the registry, and a second one for looking up the value in the memoizer.)
You can also bypass the memoizer registry entirely by using CreateMemoizer()
instead of GetMemoizer()
. Now the IMemoizer
instance is created and handed directly to you without being put into the memoizer registry.
IMemoizer<TParam, TResult>
instances have methods like:
TResult InvokeWith(TParam param)
void Remove(TParam param)
void Clear()
...in addition to some methods for instrumentation.
A class for synchronized execution of an arbitrary number of worker threads. It is used for testing the memoizer, but is generic enough to be used for testing concurrent behaviour in other objects and services.
As with the memoizer the bootstrap mechanism is a Func
or an Action
extension method, PhasedExecutionContext CreatePhasedExecutionContext
.
Pass in arguments, number of concurrent thread to run, number of iterations, and execute it.
Different PhasedExecutionContext CreatePhasedExecutionContext
objects can be merged together before execution.
%DOTNET_FRAMEWORK_4_HOME%\MSBuild %MEMOIZER_NET_HOME%\Memoizer.NET.csproj /p:Configuration=Release
...
*) Prerequisites are:
http://www.microsoft.com/net/ => "Developers" => "Install .NET Framework 4"
http://www.microsoft.com/download/en/details.aspx?displayLang=en&id=8279
WinKey -> 'cmd' -> CTRL+SHIFT+ENTER
-
Go to the project of choice, e.g the Memoizer.NET project:
cd %MEMOIZER_NET_HOME%\Memoizer.NET
-
Run NuGet
pack
command:nuget pack Memoizer.NET.csproj -symbols
...
*) Prerequisites are:
http://nuget.codeplex.com/releases/view/58939
- A mini DSL/builder for easy
Memoizer.Net.TwoPhaseExecutor
usage - NuGet distribution packages
- Optimizations...
- Can all this be accomplished using C# attributes? #lazyweb
- How do I set up NuGet properly so I can remove the silly "packages"/"lib" folders in Git? #lazyweb