ASP.NET Core Singleton 实例与瞬态实例性能

Posted

技术标签:

【中文标题】ASP.NET Core Singleton 实例与瞬态实例性能【英文标题】:ASP.NET Core Singleton instance vs Transient instance performance 【发布时间】:2019-07-14 09:15:54 【问题描述】:

在 ASP.NET Core 依赖注入中,我只是想知道注册 Singleton 实例是否会提高性能而不是注册 Transient 实例?

在我看来,对于Singleton 实例,创建新对象和依赖对象只需花费一次时间。对于Transient 实例,将针对每个服务请求重复此成本。所以Singleton 似乎更好。但是当使用Singleton 而不是Transient 时,我们可以获得多少性能呢? 提前谢谢!

【问题讨论】:

我认为这不是更好或更坏的情况,是的,是正确的情况,这在谈论依赖注入时很大程度上取决于 这不是性能问题。您应该使用您需要的解决方案。 我同意@Silvermind 的观点,但是,在不影响可观察行为以减少对象构造和图形开销的情况下,您可能更喜欢使用Singleton 而不是Transient遍历。但是,与所有优化一样,您应该衡量这是否重要。 @JoãoPauloAmorim,感谢您的回复。在我的情况下,Singleton 或 Transient 都可以使用,所以我考虑选择更好的性能。单例可能会导致内存泄漏,但我认为它的性能更好。 @Silvermind,谢谢。您是否测量了它们的性能或是否有任何链接可以参考? 【参考方案1】:

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

当资源使用是临时的并且影响最小时,瞬态范围更好。例如,如果您只进行计算,那可以是瞬态范围的,因为您不会因为拥有多个副本而耗尽任何东西。

当状态很重要时,您还想使用单例范围。如果某些东西需要在一个特定的操作之后持续存在,那么瞬态将不起作用,因为您将没有状态,因为每次注入时它基本上都会重新开始。例如,如果您尝试协调并发队列,使用信号量进行锁定,那么您肯定需要单例范围的服务。如果状态无关紧要,那么瞬态可能是更好的范围。

最后,您必须查看您的服务所依赖的其他服务。如果您需要访问范围内的服务(例如请求范围内的事物),那么单例就不合适了。虽然您可以使用服务定位器模式来访问范围内的服务,但这是一种失礼,不推荐使用。基本上,如果您的服务使用除其他单例服务之外的任何服务,则它可能应该是作用域或瞬态的。

不管长短,都使用瞬态作用域,除非你有充分的、明确的理由将其设为单例。这就是上面提到的原因:维护状态,有效利用有限资源等。如果服务将在瞬态范围内工作,并且没有充分的理由这样做,请使用瞬态范围。

现在,ASP.NET Core 的 DI 既有“瞬态”生命周期,又有“作用域”生命周期。从它们来来去去的意义上说,这两者都是“瞬态的”,但“作用域”在每个“作用域”(通常是请求)实例化一次,而“瞬态”总是在每次它时实例化被注入。在这里,您应该使用“作用域”,除非您有充分、明确的理由使用“瞬态”。

【讨论】:

+1 供您解释。但是,我有点混淆了关于每个事务的瞬态与作用域的最后一条语句。 Microsoft 声明“每次从服务容器请求它们时都会创建瞬态生命周期服务。此生命周期最适合轻量级、无状态服务。”这意味着对于 REST api,建议使用瞬态? 这里指的是你注入的服务,而不是整个 API 中的“服务”。我的观点是,你几乎应该总是使用AddScoped,除非你有一个特定的用例,实际上需要使用AddSingletonAddTransient等其他方法来注册你的服务。 很好的答案!但请注意,有一类服务,其中简单的规则“如果不存在其他原因,范围是最好的”可能会误导您。对于 .NET Core 自己的服务尤其如此。例如,您会认为 AddScoped 非常适合 HttpContextAccessor 服务。但事实并非如此。在这种情况下,您应该使用 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();。底线:检查文档(或 ***)以了解您不是自己编写的任何服务的正确生命周期! 在我的情况下,使用瞬态而不是单例,会导致至少 200 毫秒的开销。而且我没有一个构造函数可以执行其他任何字段分配。但是有一些类需要创建。由于它是一个聊天机器人服务,因此每个毫秒对用户都是可见的。在某些情况下,呼叫速度会慢 5 倍。所以在某些情况下,性能很重要。 DI系统是在每次请求时都实例化控制器还是控制器是单例的?【参考方案2】:

我知道这里的性能不是您应该寻找的,但出于好奇,我制作了一个示例基准应用程序来评估 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 [主机]:.NET 6.0.1 (6.0.121.56705), X64 RyuJIT DefaultJob:.NET 6.0.1 (6.0.121.56705),X64 RyuJIT

我的结果

Method Mean Error StdDev
CallSingletonApp 101.0 us 0.64 us 0.53 us
CallTransientApp 108.3 us 1.16 us 1.03 us

所以 AddSingleton 和 AddTransient 之间确实没有显着差异(当然,如果您的构造函数内部没有大量处理)

【讨论】:

【参考方案3】:

就像回复中提到的那样,这实际上并不是性能问题。

有关更多详细信息,请参阅下面的链接,其中对差异进行了非常详细的说明。 AddTransient, AddScoped and AddSingleton Services Differences

这对性能很重要的唯一方法是,如果你的构造函数做了很多事情。尽管在所有情况下都可以而且应该避免这种情况。

【讨论】:

感谢您的回复。就我而言,我在使用 Singleton over Transient 时会考虑性能和内存泄漏。当然,在我的情况下,两者都可以应用。 就像那里提到的那样,在这里的其他 cmets 中,这不仅仅取决于性能。应用程序中的性能增益或差异将在很大程度上取决于应用程序的进一步上下文。真正验证这一点的唯一方法是实际运行您的场景并对其进行测试,即使这样,,,,, 可能还有很多其他的东西可以给您带来更显着的性能改进。

以上是关于ASP.NET Core Singleton 实例与瞬态实例性能的主要内容,如果未能解决你的问题,请参考以下文章

《ASP.NET Core 6框架揭秘》实例演示[27]:ASP.NET Core 6 Minimal API的模拟实现

ASP.NET Core 6框架揭秘实例演示[01]: 编程初体验

为啥要在 ASP.Net Core 中获取多个 IMemoryCache 实例?

ASP.NET Core 6框架揭秘实例演示[05]:依赖注入基本编程模式

ASP.NET Core 6框架揭秘实例演示[01]: 编程初体验

《ASP.NET Core 6框架揭秘》实例演示[31]:路由高阶用法