ASP.NET Core单例实例与瞬态实例性能比较

35
在ASP.NET Core依赖注入中,我想知道注册Singleton实例是否能够提高性能而不是注册Transient实例?
据我所思,对于Singleton实例,仅需要一次创建新对象和相关的对象。对于Transient实例,这个过程将在每个服务请求中重复进行。因此Singleton似乎更好。但是使用Singleton相比Transient能够获得多少性能提升呢?
预先感谢您的回答!

3
我认为这不是好坏的问题,而是正确的问题,这在谈论依赖注入时非常重要。 - João Paulo Amorim
2
这不是性能问题。你应该使用你需要的解决方案。 - Silvermind
2
我同意@Silvermind的观点,但在可观察行为不受影响的情况下,您可能更喜欢使用“Singleton”而不是“Transient”,以减少对象构造和图遍历的开销。然而,像所有优化一样,您应该进行测量以确定这是否真的有所作用。 - Matthew
@JoãoPauloAmorim,感谢您的回复。在我的情况下,无论是Singleton还是Transient都可以使用,因此我考虑选择更好的性能。Singleton可能会导致内存泄漏,但我认为它具有更好的性能。 - tuq
在具有非常深的依赖图或依赖项初始化成本高昂的情况下,可以通过牺牲内存来实现。 - Matthew
显示剩余2条评论
3个回答

67

和其他人所说的一样,性能不应该是你做决定的考虑因素:无论哪种方式,性能都不会受到明显的影响。你需要考虑的是依赖项,包括托管和非托管的依赖项。当你使用有限的资源(例如套接字和连接)时,单例是最好的选择。如果每次注入服务(瞬态)时都需要创建一个新的套接字,那么你很快就会用完套接字,然后性能就真的会受到影响。

瞬态作用域更适合临时使用和对资源影响最小的情况。例如,如果你只是进行计算,可以使用瞬态作用域,因为通过多个副本来实现不会耗尽任何资源。

当状态非常重要时,也要使用单例作用域。如果某些东西需要在一个特定操作之后持久存在,那么瞬态作用域将无法工作,因为你将没有状态,因为每次注入时它都会从头开始。例如,如果你想协调并发队列,并使用信号量进行锁定,则肯定需要一个单例作用域的服务。如果状态并不重要,则瞬态可能是更好的作用域。

最后,您必须查看您的服务对其他服务的依赖情况。如果您需要访问范围限定的服务(例如请求范围内的服务),那么单例模式不适用。虽然您可能可以使用服务定位器模式来访问这些作用域服务,但这是一个错误,并且不被推荐。基本上,如果您的服务除了其他单例服务外使用了任何东西,则应该将其视为作用域或瞬态。

长话短说,除非您有充分明确的理由将其设置为单例,否则请使用瞬态范围。这样的理由包括维护状态、高效利用有限资源等等。如果服务可以在瞬态范围中工作,并且没有更好的理由,请使用瞬态范围。

现在,ASP.NET Core的DI具有“瞬态”和“作用域”生命周期。这两者都是“瞬态”的,也就是说它们会出现并消失,但“作用域”每次创建一个“作用域”(通常是一个请求)时都会实例化一次,而“瞬态”则每次注入时总是被实例化。在这里,除非您有充分明确的理由使用“瞬态”,否则请使用“作用域”。


1
+1 对于您的解释。不过,我对关于瞬态和作用域每个事务的最后一句话有些困惑。微软指出:“每次从服务容器请求时都会创建瞬态生命周期服务。这种生命周期最适合轻量级、无状态的服务。”这意味着对于 REST API,建议使用瞬态? - codebased
3
很棒的回答!但是我要指出,有一类服务可能会让简单的规则“如果没有其他原因,则最好使用Scoped”误导你。这在.NET Core自己的服务中尤其如此。例如,你可能会认为AddScoped对于HttpContextAccessor服务是完美的选择。但事实并非如此。在这种情况下,应该使用services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();。总之:检查文档(或StackOverflow)以获取您未编写的任何服务的正确生命周期! - Jpsy
1
在我的情况下,使用瞬态而不是单例会导致至少200毫秒的开销。而且我没有一个构造函数做任何其他事情,只有字段赋值。但是有一些需要创建的类。由于这是一个聊天机器人服务,每个毫秒对用户都是可见的。有时候,调用会变慢5倍。因此,在某些情况下,性能很重要。 - Konstantine
DI系统是否在每个请求中实例化控制器,还是控制器是单例模式? - variable
@Konstantine,200毫秒的开销对我来说听起来不太对。如果您在瞬态和单例之间观察到这样的差异,我真的认为您的服务可能出了什么问题。 - julealgon
显示剩余2条评论

4

我知道这里的性能不是您在寻找的,但出于好奇,我制作了一个样本基准应用程序来评估 AddSingleton 和 AddTransient 之间的差异,使用 BenchmarkDotNet。

我使用的解决方案可以在此处找到(请随意改进):https://github.com/iheb719/BenchmarkDifferentServicesInstances

这是我的总结:

我的笔记本电脑规格: BenchmarkDotNet=v0.13.1,OS=Windows 10.0.19042.1415(20H2 / October2020Update) Intel Core i7-9750H CPU 2.60GHz,1个CPU,12个逻辑核心和6个物理核心 .NET SDK=6.0.101 [Host]: .NET 6.0.1(6.0.121.56705),X64 RyuJIT DefaultJob:.NET 6.0.1(6.0.121.56705),X64 RyuJIT

我的结果:

方法 平均值 误差 标准差
CallSingletonApp 101.0 微秒 0.64 微秒 0.53 微秒
CallTransientApp 108.3 微秒 1.16 微秒 1.03 微秒

因此,如果构造函数内没有大量处理,AddSingleton和AddTransient之间没有显着的差异


1

如回复中所提到的,这并不是关于性能的问题。

更多细节请参见下面的链接,其中非常详细地解释了差异。 AddTransient、AddScoped和AddSingleton服务的区别

唯一会影响性能的方式是如果您的构造函数做了很多事情。然而,在所有情况下都应该避免这种情况。


谢谢您的回复。在我的情况下,我考虑使用Singleton和Transient时的性能和内存泄漏问题。当然,两者都可以应用于我的情况。 - tuq
1
就像在这里提到的和其他评论中提到的,性能并不仅仅取决于性能本身。您的应用程序中的性能增益或差异将在很大程度上取决于应用程序的进一步上下文。真正验证这一点的唯一方法是实际运行您的场景并进行测试,即使如此,可能还有许多其他因素可以为您提供更显著的性能改进。 - swforlife

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接