Кэширование данных отражения - PullRequest
49 голосов
/ 06 июля 2011

Какой лучший способ кешировать дорогие данные, полученные из рефлексии? Например, большинство быстрых сериализаторов кэшируют такую ​​информацию, поэтому им не нужно отражать каждый раз, когда они снова сталкиваются с одним и тем же типом. Они могут даже генерировать динамический метод, который они ищут по типу.

До .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 в качестве ключа. Это должно однозначно идентифицировать этот тип, не сохраняя его в памяти. Я мог бы даже избежать использования ссылочной идентичности в этой строке.

Одна проблема, которая остается с этим решением, состоит в том, что кэшированное значение может также содержать ссылку на тип. И если я использую слабую ссылку для этого, скорее всего, он истекает задолго до того, как сборка будет выгружена. И я не уверен, как дешево получить нормальную ссылку из слабой ссылки. Похоже, мне нужно провести тестирование и тестирование производительности.

Ответы [ 3 ]

35 голосов
/ 09 июля 2011

ConcurrentDictionary<WeakReference, CachedData> неверно в этом случае. Предположим, мы пытаемся кэшировать информацию для типа T, поэтому WeakReference.Target==typeof(T). CachedData, скорее всего, также будет содержать ссылку для typeof(T). Поскольку ConcurrentDictionary<TKey, TValue> хранит элементы во внутренней коллекции Node<TKey, TValue>, у вас будет цепочка сильных ссылок: ConcurrentDictionary instance -> Node instance -> Value свойство (CachedData instance) -> typeof(T). В общем случае невозможно избежать утечки памяти с помощью WeakReference в случае, когда значения могут иметь ссылки на свои ключи.

Необходимо было добавить поддержку эфемеронов , чтобы сделать такой сценарий возможным без утечек памяти. К счастью, .NET 4.0 поддерживает их, и у нас есть класс ConditionalWeakTable<TKey, TValue>. Кажется, причины ввести его близки к вашей задаче.

Этот подход также решает проблему, упомянутую в вашем обновлении, поскольку ссылка на тип будет жить ровно столько, сколько загружена сборка.

1 голос
/ 08 июля 2011

Вы должны проверить библиотеку quickflect на codeplex http://fasterflect.codeplex.com/

Вы можете использовать обычное отражение для динамического генерирования нового кода, а затем для его генерации / компиляции и последующего кэширования скомпилированной версии.Я думаю, что идея коллекционной сборки является многообещающей, чтобы избежать утечки памяти без необходимости загружать / выгружать из отдельного домена приложения.Тем не менее, утечка памяти должна быть незначительной, если вы не компилируете сотни методов.

Вот блог о динамической компиляции кода во время выполнения: 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();
        }

    }
}
0 голосов
/ 08 июля 2011

Я мог бы заявить здесь об очевидном, но:

Не кэшируют ли провайдеры обычно сериализацию данных в источник?

Так что процесс десериализации, безусловно, будет более дорогостоящим, чем просто отражение нового экземпляра?

Или я что-то пропустил?

И есть целый аргумент относительно затрат времени на упаковку и распаковку ... хотя я не уверен, действительно ли это имеет значение.

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"]);

вы хотите кешировать "штуку"?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...