Memoization is an optimization technique that enhances the performance of deterministic methods by caching their results. Metalama provides a straightforward and high-performance implementation through the [Memoize] aspect.
This aspect is limited to get-only properties and parameterless methods. The cached value of memoized methods and properties is stored in a field of the object itself, enabling a high-performance implementation using Interlocked.CompareExchange. It serves as an alternative to the Lazy<T> class, offering simpler usage and superior performance characteristics.
To memoize a property or a method:
- Add the Metalama.Patterns.Memoization package to your project.
- Apply the [Memoize] attribute to the get-only property or parameterless method.
Warning
The [Memoize] aspect doesn't guarantee that the method will be executed only once. However, it ensures that it always returns the same value or object.
Note
For nullable reference types and value types, the cached value is stored in a StrongBox<T>, adding some memory allocation overhead when many memoized properties or methods are evaluated. However, this allows for minimal memory allocation when few or none of them are evaluated.
Example: Memoization
The following example demonstrates typical use of the [Memoize] aspect. It presents a HashedBuffer class where the Hash property and the ToString method are optimized for performance. The example assumes that these members are evaluated for only a minority of HashedBuffer instances, so the hash shouldn't be pre-computed in the constructor. However, when they're evaluated, the example assumes they're evaluated often, so caching the result is beneficial. The [Memoize] aspect offers a solution that's both simpler and more performant than the Lazy<T> class.
1using Metalama.Patterns.Memoization;
2using System;
3using System.IO.Hashing;
4
5namespace Doc.Memoize_;
6
7public class HashedBuffer
8{
9 public HashedBuffer( ReadOnlyMemory<byte> buffer )
10 {
11 this.Buffer = buffer;
12 }
13
14 public ReadOnlyMemory<byte> Buffer { get; }
15
16 [Memoize]
17 public ReadOnlyMemory<byte> Hash => XxHash64.Hash( this.Buffer.Span );
18
19 [Memoize]
20 public override string ToString() => $"{{HashedBuffer ({this.Buffer.Length} bytes)}}";
21}
1using Metalama.Patterns.Memoization;
2using System;
3using System.IO.Hashing;
4using System.Runtime.CompilerServices;
5
6namespace Doc.Memoize_;
7
8public class HashedBuffer
9{
10 public HashedBuffer(ReadOnlyMemory<byte> buffer)
11 {
12 this.Buffer = buffer;
13 }
14
15 public ReadOnlyMemory<byte> Buffer { get; }
16
17 [Memoize]
18 public ReadOnlyMemory<byte> Hash
19 {
20 get
21 {
22 if (_Hash == null)
23 {
24 var value = new StrongBox<ReadOnlyMemory<byte>>(Hash_Source);
25 global::System.Threading.Interlocked.CompareExchange(ref this._Hash, value, null);
26 }
27
28 return _Hash.Value;
29 }
30 }
31
32 private ReadOnlyMemory<byte> Hash_Source => XxHash64.Hash(this.Buffer.Span);
33
34 [Memoize]
35 public override string ToString()
36 {
37 if (_ToString == null)
38 {
39 string value;
40 value = $"{{HashedBuffer ({this.Buffer.Length} bytes)}}";
41 global::System.Threading.Interlocked.CompareExchange(ref this._ToString, value, null);
42 }
43
44 return _ToString;
45 }
46
47 private StrongBox<ReadOnlyMemory<byte>> _Hash;
48 private string _ToString;
49}
Memoization vs. caching
Memoization can be considered a simple form of caching. The [Memoize] aspect is often a no-brainer—it's extremely simple to use and requires no infrastructure.
| Factor | Memoization | Caching |
|---|---|---|
| Scope | Local to a single class instance within the current process | Either local or shared, when run as an external service such as Redis |
| Unicity of cache items | Specific to the current instance or type | Based on explicit string cache keys |
| Complexity and overhead | Minimal overhead | Significant overhead related to the generation of cache keys and, in the case of distributed caching, serialization |
| Expiration and invalidation | No expiration or invalidation | Advanced and configurable expiration policies and invalidation APIs |