Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Caching reflection data

What's the best way to cache expensive data obtained from reflection? For example most fast serializers cache such information so they don't need to reflect every time they encounter the same type again. They might even generate a dynamic method which they look up from the type.

Before .net 4

Traditionally I've used a normal static dictionary for that. For example:

private static ConcurrentDictionary<Type, Action<object>> cache;  public static DoSomething(object o) {     Action<object> action;     if(cache.TryGetValue(o.GetType(), out action)) //Simple lookup, fast!     {         action(o);     }     else     {         // Do reflection to get the action         // slow     } }  

This leaks a bit of memory, but since it does that only once per Type and types lived as long as the AppDomain I didn't consider that a problem.

Since .net 4

But now .net 4 introduced Collectible Assemblies for Dynamic Type Generation. If I ever used DoSomething on an object declared in the collectible assembly that assembly won't ever get unloaded. Ouch.

So what's the best way to cache per type information in .net 4 that doesn't suffer from this problem? The easiest solution I can think of is a:

private static ConcurrentDictionary<WeakReference, TCachedData> cache. 

But the IEqualityComparer<T> I'd have to use with that would behave very strangely and would probably violate the contract too. I'm not sure how fast the lookup would be either.

Another idea is to use an expiration timeout. Might be the simplest solution, but feels a bit inelegant.


In the cases where the type is supplied as generic parameter I can use a nested generic class which should not suffer from this problem. But his doesn't work if the type is supplied in a variable.

class MyReflection {     internal Cache<T>     {         internal static TData data;     }      void DoSomething<T>()     {         DoSomethingWithData(Cache<T>.data);         //Obviously simplified, should have similar creation logic to the previous code.     } } 

Update: One idea I've just had is using Type.AssemblyQualifiedName as the key. That should uniquely identify that type without keeping it in memory. I might even get away with using referential identity on this string.

One problem that remains with this solution is that the cached value might keep a reference to the type too. And if I use a weak reference for that it will most likely expire far before the assembly gets unloaded. And I'm not sure how cheap it is to Get a normal reference out of a weak reference. Looks like I need to do some testing and benchmarking.

like image 340
CodesInChaos Avatar asked Jul 06 '11 18:07

CodesInChaos


People also ask

What is caching in data?

In computing, a cache is a high-speed data storage layer which stores a subset of data, typically transient in nature, so that future requests for that data are served up faster than is possible by accessing the data's primary storage location.

What is the purpose of caching?

Caching Data is a process that stores multiple copies of data or files in a temporary storage location—or cache—so they can be accessed faster.

What is caching and how it works?

Caching is the process of storing copies of files in a cache, or temporary storage location, so that they can be accessed more quickly. Technically, a cache is any temporary storage location for copies of files or data, but the term is often used in reference to Internet technologies.

What is data caching in C#?

Caching enables you to store data in memory for rapid access. When the data is accessed again, applications can get the data from the cache instead of retrieving it from the original source. This can improve performance and scalability.


2 Answers

ConcurrentDictionary<WeakReference, CachedData> is incorrect in this case. Suppose we are trying to cache info for type T, so WeakReference.Target==typeof(T). CachedData most likely will contain reference for typeof(T) also. As ConcurrentDictionary<TKey, TValue> stores items in the internal collection of Node<TKey, TValue> you will have chain of strong references: ConcurrentDictionary instance -> Node instance -> Value property (CachedData instance) -> typeof(T). In general it is impossible to avoid memory leak with WeakReference in the case when Values could have references to their Keys.

It was necessary to add support for ephemerons to make such scenario possible without memory leaks. Fortunately .NET 4.0 supports them and we have ConditionalWeakTable<TKey, TValue> class. It seems the reasons to introduce it are close to your task.

This approach also solves problem mentioned in your update as reference to Type will live exactly as long as Assembly is loaded.

like image 146
Ivan Danilov Avatar answered Sep 19 '22 22:09

Ivan Danilov


You should check out the fasterflect libary

You could use normal reflection to dynamically generate new code & then emit/compile it and then caching the compiled version. I think the collectible assembly idea is promising, to avoid the memory leak without having to load/unload from a separate appdomain. However, the memory leak should be negligible unless you're compiling hundreds of methods.

Here's a blogpost on dynamically compiling code at runtime: http://introspectingcode.blogspot.com/2011/06/dynamically-compile-code-at-runtime.html

Below is a similar concurrent dictionary approach I've used in the past to store the MethodInfo/PropertyInfo objects & it did seem to be a faster, but I think that was in an old version of Silverlight. I believe .Net has it's own internal reflection cache that makes it unnecessary.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Collections.Concurrent;  namespace NetSteps.Common.Reflection {     public static class Reflection     {         private static ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>> reflectionPropertyCache = new ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>>();         public static List<PropertyInfo> FindClassProperties(Type objectType)         {             if (reflectionPropertyCache.ContainsKey(objectType))                 return reflectionPropertyCache[objectType].Values.ToList();              var result = objectType.GetProperties().ToDictionary(p => p.Name, p => p);              reflectionPropertyCache.TryAdd(objectType, result);              return result.Values.ToList();         }      } } 
like image 36
Devin Garner Avatar answered Sep 20 '22 22:09

Devin Garner