AOP – FIFO Memoizer with PostSharp

Yan Cui

I help clients go faster for less using serverless technologies.

Back in my first post on AOP, I mentioned the Memoizer on D. Patrick Caldwell’s blog, well, today I came across a situation where I was able to use it but first I needed to make a few modifications because the original implementation didn’t satisfy some of my requirements:

  • There is no cap on the size of the dictionary, I want to avoid a situations where my application uses too much memory, and in the extreme case throws a OutOfMemoryException;
  • There is one static cache shared across all methods, so even with a cap on the size of the dictionary it won’t stop one method from taking up all the available spaces in the dictionary
  • The order of the memos is not kept, so you won’t be able to implement have FIFO strategy for removing old memo entries

In case you’re wondering why I would require these functionalities, I’m building an image viewer with zip support and it doesn’t make sense to load all the images in the zip file into memory at the start (the zip files can be typically hundreds of megs big).

The images are sorted, and as you’re navigating through the images it behaves like a LinkedList anyway, so I’m implementing a sliding window based on where you are in the list and load the previous and next 10 images into cache. Therefore a FIFO memoizer can help reduce the amount of reads from the zip file I need to perform (decompression is an expensive operation).

And here’s the modified version of the Memoizer attribute:

[Serializable]
[AttributeUsage(AttributeTargets.Method)] // only allowed on methods
[DebuggerStepThrough]
public sealed class MemoizeAttribute : OnMethodInvocationAspect
{
    private const int DefaultMemoSize = 100; // default memo size is 100
    // private field to store memos
    private readonly Dictionary<string, object> _memos = new Dictionary<string, object>();
    // private queue to keep track of the order the memos are put in
    private readonly Queue<string> _queue = new Queue<string>();

    #region Constructors
    public MemoizeAttribute() : this(DefaultMemoSize)
    {
    }
    public MemoizeAttribute(int memoSize)
    {
        MemoSize = memoSize;
    }
    #endregion

    public int MemoSize { get; set; } // how many items to keep in the memo

    // intercept the method invocation
    public override void OnInvocation(MethodInvocationEventArgs eventArgs)
    {
        // get the arguments that were passed to the method
        var args = eventArgs.GetArgumentArray();
        var keyBuilder = new StringBuilder();
        // append the hashcode of each arg to the key
        // this limits us to value types (and strings)
        // i need a better way to do this (and preferably
        // a faster one)
        for (var i = 0; i < args.Length; i++)
            keyBuilder.Append(args[i].GetHashCode());
        var key = keyBuilder.ToString();
        // if the key doesn't exist, invoke the original method
        // passing the original arguments and store the result
        if (!_memos.ContainsKey(key))
        {
            _memos[key] = eventArgs.Method.Invoke(eventArgs.Instance, args);
            _queue.Enqueue(key);
            // if we've exceeded the set memo size, then remove the earliest entry
            if (_queue.Count > MemoSize)
            {
                var deQueueKey = _queue.Dequeue();
                _memos.Remove(deQueueKey);
            }
        }
        // return the memo
        eventArgs.ReturnValue = _memos[key];
    }
}

And here’s how you use it, remember, the size cap applies to all the calls to this method (not limited to a particular instance):

[Memoize(5)]
private MemoryStream GetImageStream(ZipFile zipFile, string fileName)
{
    using (var memoryStream = new MemoryStream())
    {
        zipFile[fileName].Extract(memoryStream);
        return memoryStream;
    }
}

 

Whenever you’re ready, here are 4 ways I can help you:

  1. If you want a one-stop shop to help you quickly level up your serverless skills, you should check out my Production-Ready Serverless workshop. Over 20 AWS Heroes & Community Builders have passed through this workshop, plus 1000+ students from the likes of AWS, LEGO, Booking, HBO and Siemens.
  2. If you want to learn how to test serverless applications without all the pain and hassle, you should check out my latest course, Testing Serverless Architectures.
  3. If you’re a manager or founder and want to help your team move faster and build better software, then check out my consulting services.
  4. If you just want to hang out, talk serverless, or ask for help, then you should join my FREE Community.

 


1 thought on “AOP – FIFO Memoizer with PostSharp”

  1. Pingback: AOP with F# – Memorization using higher-order functions | theburningmonk.com

Leave a Comment

Your email address will not be published. Required fields are marked *