Данные отражения кэширования
Какой лучший способ кэшировать дорогостоящие данные, полученные от отражения? Например, самые быстрые сериализаторы кэшируют такую информацию, поэтому им не нужно отражать каждый раз, когда они снова сталкиваются с одним и тем же типом. Они могут даже генерировать динамический метод, который они ищут из типа.
До .net 4
Традиционно я использовал для этого обычный статический словарь. Например:
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
}
}
Это утечка бит памяти, но поскольку он делает это только один раз для каждого типа и типов, до тех пор, пока AppDomain
я не рассматривал эту проблему.
Так как .net 4
Но теперь .net 4 представила Коллекционные сборки для генерации динамического типа. Если я когда-либо использовал DoSomething
для объекта, объявленного в сборной сборке, сборка никогда не будет выгружена. Уч.
Итак, какой лучший способ кэшировать информацию о типе в .net 4, которая не страдает от этой проблемы? Самое легкое решение, о котором я могу думать, это:
private static ConcurrentDictionary<WeakReference, TCachedData> cache.
Но IEqualityComparer<T>
, с которым мне пришлось бы использовать, это будет вести себя очень странно и, вероятно, также нарушит контракт. Я не уверен, насколько быстрым будет поиск.
Другая идея - использовать тайм-аут истечения срока действия. Может быть самым простым решением, но он чувствует себя немного неэлегантным.
В тех случаях, когда тип предоставляется как общий параметр, я могу использовать вложенный общий класс, который не должен страдать от этой проблемы. Но его не работает, если тип указан в переменной.
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.
}
}
Обновить. Одна из моих идей - использовать Type.AssemblyQualifiedName
как ключ. Это должно однозначно идентифицировать этот тип, не сохраняя его в памяти. Я мог бы даже уйти с использованием ссылочной идентификации в этой строке.
Одна из проблем, которая остается в этом решении, заключается в том, что кешированное значение может также содержать ссылку на этот тип. И если я использую слабую ссылку для этого, она, скорее всего, истечет задолго до того, как сборка будет выгружена. И я не уверен, насколько дешево получить нормальную ссылку из слабой ссылки. Похоже, мне нужно провести тестирование и бенчмаркинг.
Ответы
Ответ 1
ConcurrentDictionary<WeakReference, CachedData>
неверно в этом случае. Предположим, мы пытаемся кэшировать информацию для типа T, поэтому WeakReference.Target==typeof(T)
. CachedData скорее всего будет содержать ссылку для typeof(T)
. Поскольку ConcurrentDictionary<TKey, TValue>
хранит элементы во внутренней коллекции Node<TKey, TValue>
, у вас будет цепочка сильных ссылок: ConcurrentDictionary
instance → Node
instance → Value
property (CachedData
instance) → typeof(T)
. В общем случае невозможно избежать утечки памяти с помощью WeakReference в случае, когда значения могут иметь ссылки на их ключи.
Необходимо было добавить поддержку эфемеронов, чтобы сделать такой сценарий возможным без утечек памяти. К счастью,.NET 4.0 поддерживает их, и у нас есть класс ConditionalWeakTable<TKey, TValue>
. Кажется, причины, чтобы представить его, близки к вашей задаче.
Этот подход также решает проблему, указанную в вашем обновлении, поскольку ссылка на Type будет работать точно столько же, сколько будет загружена сборка.
Ответ 2
Вы должны проверить более быстрый библиотека
Вы можете использовать нормальное отражение для динамического создания нового кода & затем сгенерируйте/скомпилируйте его и затем кэшируйте скомпилированную версию. Я думаю, что идея коллекционной сборки является многообещающей, чтобы избежать утечки памяти без необходимости загружать/выгружать из отдельного домена приложения. Однако утечка памяти должна быть незначительной, если вы не компилируете сотни методов.
Вот блог о динамической компиляции кода во время выполнения: http://introspectingcode.blogspot.com/2011/06/dynamically-compile-code-at-runtime.html
Ниже приведен аналогичный подход к параллельному словарю, который я использовал в прошлом для хранения объектов MethodInfo/PropertyInfo & Казалось, что это было быстрее, но я думаю, что это было в старой версии Silverlight. Я считаю, что .Net имеет свой собственный внутренний кэш отражения, который делает его ненужным.
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();
}
}
}
Ответ 3
Я мог бы заявить очевидное здесь, но:
Не кэшировать поставщиков обычно сериализуют данные в источник?
Таким образом, процесс десериализации будет более дорогостоящим, чем просто отражением нового экземпляра?
Или я что-то пропустил?
И вот весь аргумент в отношении бокса и времени распаковки... не уверен, действительно ли это имеет значение.
Edit:
Как насчет этого (надеюсь, это объясняет проблему немного лучше)...
Dictionary<string, Type> typecache = new Dictionary<string, Type>();
// finding a type from say a string that points at a type in an assembly not referrenced
// very costly but we can cache that
Type myType = GetSomeTypeWithReflection();
typecache.Add("myType", myType);
// creating an instance you can use very costly
MyThingy thingy = Activator.CreateInstance(typecache["myType"]);
Вы хотите кэшировать "thingy"?