Общая фабрика: кэш против повторяющихся мгновений - PullRequest
1 голос
/ 31 мая 2011

У меня есть универсальная фабрика, которая кэширует экземпляр перед его возвратом (упрощенный код):

static class Factory<T>
    where T : class
{
    private static T instance;

    public static T GetInstance()
    {
        if (instance == null) instance = new T();
        return instance;
    }
}

Я хочу заменить такой подход на не кэширующий, чтобы показать, что кэширование не имеет смысла в вопросахпроизводительность экземпляра (я считаю, создание нового объекта очень дешево).

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

Ответы [ 2 ]

2 голосов
/ 28 ноября 2012

Вот мои два цента, хотя я согласен с ответами Джаугфина и Даниэля Хилгарта.Использование кэширования универсальных типов с использованием статического члена таким способом интуитивно создает дополнительные параллельные типы для каждого типа, который кэшируется, но важно понимать, как это работает по-разному для ссылочных типов и типов значений.Для ссылочных типов как T создаваемые дополнительные универсальные типы должны использовать меньше ресурсов, чем эквивалентное использование типа значения.

Так когда же вам следует использовать метод универсального типа для создания кэша?Ниже приведены несколько важных критериев, которые я использую.1. Вы хотите разрешить кэширование отдельных экземпляров каждого интересующего вас класса.2. Вы хотите использовать ограничения универсального типа времени компиляции для обеспечения соблюдения правил для типов, используемых в кэше.С помощью ограничений типа вы можете обеспечить необходимость для экземпляра реализовать несколько интерфейсов, не определяя базовый тип для этих классов.3. Вам не нужно удалять элементы из кэша в течение всего срока службы домена приложения.

Кстати, одним из терминов, который может быть полезен для поиска, является «Взрыв кода», который является общим термином, используемым дляопределить случаи, когда для выполнения регулярно выполняемой задачи требуется значительный объем кода, который обычно растет линейно или хуже с ростом требований проекта.Что касается обобщенных типов, я слышал и буду использовать термин «взрыв типов» для описания распространения типов, когда вы начинаете комбинировать и составлять несколько универсальных типов.

Еще один важный момент заключается в том, что в этихВ этом случае фабрика и кеш всегда могут быть разделены, и в большинстве случаев им может быть предоставлен идентичный интерфейс, который позволит вам заменить фабрику (новый экземпляр на вызов) или кеш, который по существу оборачивает фабрику и делегирует через один и тот же интерфейс вслучаи, когда вы хотите использовать один или другой в зависимости от таких вещей, как проблемы взрыва типа.Ваш кэш может также взять на себя большую ответственность, такую ​​как более сложная стратегия кеширования, где, возможно, отдельные типы кэшируются по-разному (например, ссылочные типы и типы значений).Если вам интересно об этом, уловка заключается в том, чтобы определить ваш общий класс, который выполняет кэширование, как частный класс внутри фактического конкретного типа, который реализует интерфейс для вашей фабрики.Я могу привести пример, если вы хотите.

Обновление с примером кода в соответствии с просьбой:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace CacheAndFactory
{
    class Program
    {
        private static int _iterations = 1000;

        static void Main(string[] args)
        {
            var factory = new ServiceFactory();

            // Exercise the factory which implements IServiceSource
            AccessAbcTwoTimesEach(factory);

            // Exercise the generics cache which also implements IServiceSource
            var cache1 = new GenericTypeServiceCache(factory);
            AccessAbcTwoTimesEach(cache1);

            // Exercise the collection based cache which also implements IServiceSource
            var cache2 = new CollectionBasedServiceCache(factory);
            AccessAbcTwoTimesEach(cache2);

            Console.WriteLine("Press any key to continue");
            Console.ReadKey();
        }

        public static void AccessAbcTwoTimesEach(IServiceSource source)
        {
            Console.WriteLine("Excercise " + source.GetType().Name);

            Console.WriteLine("1st pass - Get an instance of A, B, and C through the source and access the DoSomething for each.");
            source.GetService<A>().DoSomething();
            source.GetService<B>().DoSomething();
            source.GetService<C>().DoSomething();
            Console.WriteLine();

            Console.WriteLine("2nd pass - Get an instance of A, B, and C through the source and access the DoSomething for each.");
            source.GetService<A>().DoSomething();
            source.GetService<B>().DoSomething();
            source.GetService<C>().DoSomething();
            Console.WriteLine();

            var clock = Stopwatch.StartNew();

            for (int i = 0; i < _iterations; i++)
            {
                source.GetService<A>();
                source.GetService<B>();
                source.GetService<C>();
            }

            clock.Stop();

            Console.WriteLine("Accessed A, B, and C " + _iterations + " times each in " + clock.ElapsedMilliseconds + "ms through " + source.GetType().Name + ".");
            Console.WriteLine();
            Console.WriteLine();
        }
    }

    public interface IService
    {
    }

    class A : IService
    {
        public void DoSomething() { Console.WriteLine("A.DoSomething(), HashCode: " + this.GetHashCode()); }
    }

    class B : IService
    {
        public void DoSomething() { Console.WriteLine("B.DoSomething(), HashCode: " + this.GetHashCode()); }
    }

    class C : IService
    {
        public void DoSomething() { Console.WriteLine("C.DoSomething(), HashCode: " + this.GetHashCode()); }
    }

    public interface IServiceSource
    {
        T GetService<T>() 
            where T : IService, new();
    }

    public class ServiceFactory : IServiceSource
    {
        public T GetService<T>() 
            where T : IService, new()
        {
            // I'm using Activator here just as an example
            return Activator.CreateInstance<T>();
        }
    }

    public class GenericTypeServiceCache : IServiceSource
    {
        IServiceSource _source;

        public GenericTypeServiceCache(IServiceSource source)
        {
            _source = source;
        }

        public T GetService<T>() 
            where T : IService, new()
        {
            var serviceInstance = GenericCache<T>.Instance;
            if (serviceInstance == null)
            {
                serviceInstance = _source.GetService<T>();
                GenericCache<T>.Instance = serviceInstance;
            }

            return serviceInstance;
        }

        // NOTE: This technique will cause all service instances cached here 
        // to be shared amongst all instances of GenericTypeServiceCache which
        // may not be desireable in all applications while in others it may
        // be a performance enhancement.
        private class GenericCache<T>
        {
            public static T Instance;
        }
    }

    public class CollectionBasedServiceCache : IServiceSource
    {
        private Dictionary<Type, IService> _serviceDictionary;
        IServiceSource _source;

        public CollectionBasedServiceCache(IServiceSource source)
        {
            _serviceDictionary = new Dictionary<Type, IService>();
            _source = source;
        }

        public T GetService<T>()
            where T : IService, new()
        {

            IService serviceInstance;
            if (!_serviceDictionary.TryGetValue(typeof(T), out serviceInstance))
            {
                serviceInstance = _source.GetService<T>();
                _serviceDictionary.Add(typeof(T), serviceInstance);
            }

            return (T)serviceInstance;
        }

        private class GenericCache<T>
        {
            public static T Instance;
        }
    }
}

По сути, приведенный выше код представляет собой консольное приложение, которое имеет концепцию интерфейсаобеспечить абстракцию источника службы.Я использовал общее ограничение IService, чтобы показать пример того, как это может иметь значение.Я не хочу набирать или публиковать 1000 отдельных определений типов, поэтому я сделал следующую лучшую вещь и создал три класса - A, B и C - и обращался к ним каждый 1000 раз, используя каждую технику - повторяющиеся экземпляры, кэш универсального типа икэш на основе коллекции.

При небольшом наборе обращений разница незначительна, но, конечно, мой конструктор службы является упрощенным (конструктор по умолчанию без параметров), поэтому он ничего не вычисляет, не обращается к базе данных, конфигурации доступа или любому извещи, которые типичные классы обслуживания делают, когда они построены.Если это не так, то преимущества стратегии кэширования, очевидно, будут полезны для производительности.Кроме того, при доступе даже к конструктору по умолчанию в тех случаях, когда существует 1000000 обращений, по-прежнему существует существенная разница между отсутствием кэширования и кэшированием (3 с: 120 мс), поэтому урок заключается в том, что если вы выполняете доступ с большим объемом или сложные вычисления, которые требуют частого доступачерез фабрику кэширование будет не только выгодным, но и будет зависеть от необходимости, в зависимости от того, влияет ли это на восприятие пользователя или на чувствительные ко времени бизнес-процессы, в противном случае выгоды незначительныВажно помнить, что вам нужно беспокоиться не только о времени создания экземпляра, но и о загрузке сборщика мусора.

1 голос
/ 31 мая 2011

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

Но чтобы ответить на ваш вопрос: кэширование объектов всегда будет быстрее.Хранение их в LinkedList или что-то подобное уменьшит накладные расходы, и производительность не должна уменьшаться при увеличении количества объектов.

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

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