AOP – FIFO Memoizer with PostSharp

Yan Cui

I help clients go faster for less using serverless technologies.

This article is brought to you by

The real-time data platform that empowers developers to build innovative products faster and more reliably than ever before.

Learn more

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. Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
  2. Do you want to know how to test serverless architectures with a fast dev & test loop? Check out my latest course, Testing Serverless Architectures and learn the smart way to test serverless.
  3. I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
  4. Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.

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 *