我发现这篇文章是关于 Lazy
:C#4.0中的惰性-惰性
使用惰性对象具有最佳性能的最佳实践是什么?有人可以指出我在实际应用中的实际用途吗?换句话说,什么时候应该使用它?
get { if (foo == null) foo = new Foo(); return foo; }
它不是线程安全的,而Lazy<T>
默认情况下是线程安全的。
我发现这篇文章是关于 Lazy
:C#4.0中的惰性-惰性
使用惰性对象具有最佳性能的最佳实践是什么?有人可以指出我在实际应用中的实际用途吗?换句话说,什么时候应该使用它?
get { if (foo == null) foo = new Foo(); return foo; }
它不是线程安全的,而Lazy<T>
默认情况下是线程安全的。
Answers:
通常,当您想在第一次实际使用某个实例时实例化它。这将创建它的成本延迟到需要时/而不是始终产生成本。
通常,当可以使用或不使用该对象并且构造它的成本很重要时,这是优选的。
Lazy<T>
。但是,要创建每个属性,我需要进行线性内插(或双线性内插),这很简单,但确实需要一定的成本。(你会建议我去,做自己的实验?)
您应该尝试避免使用Singleton,但是如果需要,Lazy<T>
可以轻松实现懒惰的,线程安全的Singleton:
public sealed class Singleton
{
// Because Singleton's constructor is private, we must explicitly
// give the Lazy<Singleton> a delegate for creating the Singleton.
static readonly Lazy<Singleton> instanceHolder =
new Lazy<Singleton>(() => new Singleton());
Singleton()
{
// Explicit private constructor to prevent default public constructor.
...
}
public static Singleton Instance => instanceHolder.Value;
}
延迟加载非常有用的一个现实世界的好例子是使用ORM(对象关系映射器),例如Entity Framework和NHibernate。
假设您有一个实体客户,该客户具有名称,电话号码和订单的属性。Name和PhoneNumber是常规字符串,但是Orders是一个导航属性,它返回客户曾经做过的每个订单的列表。
您通常可能需要遍历所有客户,并获取他们的姓名和电话号码以致电给他们。这是一个非常快速和简单的任务,但是想象一下,如果您每次创建一个客户,它都会自动进行并执行复杂的联接以返回数千个订单。最糟糕的是,您甚至都不会使用这些订单,因此完全浪费了资源!
这是延迟加载的理想场所,因为如果Order属性是延迟的,除非您确实需要,否则它将不会获取所有客户的订单。您可以枚举在Order属性耐心睡眠时,Customer对象仅获得其名称和电话号码,以备您需要时使用。
Db.Customers.Include("Orders")
。这将导致订单联接在那一刻而不是在Customer.Orders
首次使用该属性时执行。也可以通过DbContext禁用延迟加载。
我一直在考虑使用Lazy<T>
属性来帮助改善我自己的代码的性能(并进一步了解它)。我来这里是为了寻找有关何时使用它的答案,但是似乎我走到哪里都有类似的短语:
使用惰性初始化可以延迟大型对象或资源密集型对象的创建或资源密集型任务的执行,尤其是在程序生命周期中可能不会发生此类创建或执行时。
我有点困惑,因为我不确定在哪里划界线。例如,我认为线性插值是一种相当快速的计算,但是如果我不需要这样做,那么惰性初始化可以帮助我避免这样做,这值得吗?
最后,我决定尝试自己的测试,并认为我会在这里分享结果。不幸的是,我并不是真正从事此类测试的专家,因此,我很高兴收到提出改进建议的意见。
描述
对于我的情况,我特别感兴趣的是看看Lazy Properties是否可以帮助改进代码中进行大量插值的部分(大部分未使用),因此我创建了一个比较3种方法的测试。
我为每种方法创建了一个单独的测试类,该类具有20个测试属性(称为t-properties)。
测试结果以毫秒为单位,是50个实例或20个属性获取值的平均值。然后每个测试运行5次。
测试1结果:实例化(平均50个实例化)
Class 1 2 3 4 5 Avg % ------------------------------------------------------------------------ GetInterp 0.005668 0.005722 0.006704 0.006652 0.005572 0.0060636 6.72 InitInterp 0.08481 0.084908 0.099328 0.098626 0.083774 0.0902892 100.00 InitLazy 0.058436 0.05891 0.068046 0.068108 0.060648 0.0628296 69.59
测试2结果:首次获得(平均20个属性获得)
Class 1 2 3 4 5 Avg % ------------------------------------------------------------------------ GetInterp 0.263 0.268725 0.31373 0.263745 0.279675 0.277775 54.38 InitInterp 0.16316 0.161845 0.18675 0.163535 0.173625 0.169783 33.24 InitLazy 0.46932 0.55299 0.54726 0.47878 0.505635 0.510797 100.00
测试3结果:第二次获取(平均20个属性获取)
Class 1 2 3 4 5 Avg % ------------------------------------------------------------------------ GetInterp 0.08184 0.129325 0.112035 0.097575 0.098695 0.103894 85.30 InitInterp 0.102755 0.128865 0.111335 0.10137 0.106045 0.110074 90.37 InitLazy 0.19603 0.105715 0.107975 0.10034 0.098935 0.121799 100.00
观察结果
GetInterp
实例化最快,因为它没有执行任何操作。InitLazy
实例化比InitInterp
建议设置惰性属性的开销要快于我的线性插值计算要快。但是,我在这里有点困惑,因为InitInterp
应该执行20个线性插值(以设置t属性),但是实例化(测试1)仅花费0.09毫秒,而GetInterp
仅执行一次线性插值则花费0.28毫秒第一次(测试2),第二次(测试3)需要0.1毫秒。
它InitLazy
比GetInterp
第一次获得属性要花几乎两倍的时间,而这InitInterp
是最快的,因为它在实例化期间填充了属性。(至少那是应该做的,但是为什么它的实例化结果比单个线性插值要快得多?什么时候才进行这些插值?)
不幸的是,我的测试中似乎正在进行一些自动代码优化。应该要GetInterp
同样的时间去为它的第二次首次属性,但它显示为2倍以上更快。看起来这种优化也影响了其他类,因为它们都花费了相同的时间进行测试3。但是,这种优化也可能发生在我自己的生产代码中,这可能也是一个重要的考虑因素。
结论
尽管某些结果是预期的,但也可能由于代码优化而产生了一些非常有趣的意外结果。即使对于看起来像在构造函数中完成大量工作的类,实例化结果也表明,与获得double属性相比,它们的创建速度仍然非常快。尽管该领域的专家可以发表评论并进行更彻底的研究,但我个人的感觉是,我需要对生产代码再次进行此测试,以便检查那里可能还会进行哪种优化。但是,我希望这InitInterp
可能是要走的路。
lazy
必须做一些额外的记账工作,因此InitLazy
会比其他解决方案使用更多的内存。在检查它是否已经有值的同时,它对每次访问的性能可能也会有轻微的影响。巧妙的技巧可以消除这些开销,但是需要IL中的特殊支持。(Haskell通过使每个惰性值成为函数调用来执行此操作;一旦生成该值,它将替换为每次都会返回该值的函数。)
只是指出Mathew发布的示例
public sealed class Singleton
{
// Because Singleton's constructor is private, we must explicitly
// give the Lazy<Singleton> a delegate for creating the Singleton.
private static readonly Lazy<Singleton> instanceHolder =
new Lazy<Singleton>(() => new Singleton());
private Singleton()
{
...
}
public static Singleton Instance
{
get { return instanceHolder.Value; }
}
}
在懒惰者出生之前,我们可以这样进行:
private static object lockingObject = new object();
public static LazySample InstanceCreation()
{
if(lazilyInitObject == null)
{
lock (lockingObject)
{
if(lazilyInitObject == null)
{
lazilyInitObject = new LazySample ();
}
}
}
return lazilyInitObject ;
}
从MSDN:
使用Lazy实例可以延迟大型对象或资源密集型对象的创建或资源密集型任务的执行,尤其是在程序生命周期中可能不会发生此类创建或执行时。
除了James Michael Hare的答案之外,Lazy还提供了线程安全的值初始化。看一下LazyThreadSafetyMode枚举MSDN条目,该条目描述此类的各种类型的线程安全模式。
您应该看一下这个例子,以了解懒加载架构
private readonly Lazy<List<int>> list = new Lazy<List<int>>(() =>
{
List<int> configList = new List<int>(Thread.CurrentThread.ManagedThreadId);
return configList;
});
public void Execute()
{
list.Value.Add(0);
if (list.IsValueCreated)
{
list.Value.Add(1);
list.Value.Add(2);
foreach (var item in list.Value)
{
Console.WriteLine(item);
}
}
else
{
Console.WriteLine("Value not created");
}
}
->输出-> 0 1 2
但是如果这段代码没有写“ list.Value.Add(0);”
输出->未创建值
get { if (foo == null) foo = new Foo(); return foo; }
。并且有无数的可能使用它的地方……