我有一个通用工厂,在返回它之前缓存一个实例(简化代码):
static class Factory<T>
where T : class
{
private static T instance;
public static T GetInstance()
{
if (instance == null) instance = new T();
return instance;
}
}
我想用非缓存方法替换这种方法,以表明缓存在实例化性能方面毫无意义(我相信新对象创建非常便宜)。
所以我想编写一个负载测试,它将创建一个动态的,仅运行时类型的交易,并将其加载到我的工厂。一个会缓存,另一个 - 不会。
答案 0 :(得分:2)
虽然我同意jgauffin和Daniel Hilgarth的回答,但这是我的两分钱。以这种方式使用静态成员使用泛型类型缓存将直观地为每个缓存的类型创建其他并行类型,但重要的是要了解它对于引用和值类型的工作方式有何不同。对于作为T的引用类型,生成的其他泛型类型应使用比值类型的等效使用更少的资源。
那么什么时候应该使用泛型类型技术来生成缓存?以下是我使用的一些重要标准。 1.您希望允许缓存每个感兴趣类的单个实例。 2.您希望使用编译时泛型类型约束来强制执行缓存中使用的类型的规则。使用类型约束,您可以强制实例需要实现多个接口,而无需为这些类定义基类型。 3.在AppDomain的生命周期内,您无需从缓存中删除项目。
顺便说一句,可能有用的一个术语是“Code Explosion”,它是一个通用术语,用于定义需要大量代码来执行某些定期发生的任务并且通常线性增长或更糟的情况随着项目要求的增长。在泛型类型方面,我听说并且通常会使用术语“类型爆炸”来描述类型的扩散,因为您开始组合并组成几种泛型类型。
另一个重点是,在这些情况下,工厂和缓存总是可以分开,在大多数情况下,它们可以被赋予相同的接口,这将允许您替换工厂(每个调用的新实例)或基本上的缓存在你想要使用其中一个或另一个的情况下,根据类型爆炸问题等事情,将工厂和委托包装起来。您的缓存还可以承担更多责任,例如更复杂的缓存策略,其中特定类型可能以不同方式缓存(例如,引用类型与值类型)。如果您对此感到好奇,那就是定义您的泛型类,它将缓存作为实现具体类型的私有类来实现工厂的接口。如果你愿意,我可举个例子。
根据要求使用示例代码进行更新:
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次 - 重复实例化,泛型类型缓存,以及基于集合的缓存。
通过一小组访问,差异可以忽略不计,但当然我的服务构造函数是简单的(默认的无参数构造函数),因此它不会计算任何东西,访问数据库,访问配置或典型服务类所做的任何事情。他们是建造的。如果不是这种情况,那么某些缓存策略的好处显然会对性能有益。此外,当访问存在1,000,000次访问的caes中的默认构造函数时,不缓存和缓存(3s:120ms)之间仍然存在显着差异,因此,如果您正在进行高容量访问或需要频繁访问的复杂计算通过工厂,缓存不仅有益,而且根据是否影响用户感知或时间敏感的业务流程来确定必要性,否则利益可以忽略不计。需要记住的重要一点是,不仅需要担心实例化时间,还要考虑垃圾收集器的负载。
答案 1 :(得分:1)
听起来我的同事希望过早优化。缓存对象很少是个好主意。实例化很便宜,我只会在已证明速度更快的情况下缓存对象。高性能套接字服务器就是这种情况。
但回答你的问题:缓存对象总是会更快。将它们保存在LinkedList
或类似的东西中会使开销变小,性能不会随着对象数量的增加而减少。
因此,如果您愿意接受更大的内存消耗并增加复杂性,请选择缓存。