.NET 应用程序的性能一直是开发人员关注的重点。性能优化是一个复杂而且需要经验的过程。在本文中,我们将讨论如何优化 C#/.NET 应用程序的性能,包括从 CLR 调优到代码优化,以及一些示例代码来帮助你更好地理解。
CLR 调优
CLR 是 .NET 应用程序的关键组成部分。CLR 负责管理内存,执行垃圾回收,并提供使用 .NET 语言编写的应用程序的基本运行时支持。CLR 能够自动识别哪些代码执行频繁,从而优化代码的执行。我们可以通过以下方式来优化 CLR 的性能。
预编译程序集
.NET 程序在第一次运行时需要进行 JIT 编译。这将导致应用程序启动速度较慢。我们可以使用 NGen(Native Image Generator)工具将应用程序预编译,从而减少启动时间,并提高性能。
首先,我们需要确定我们需要加速的程序集。可以使用以下命令列出我们的.NET应用程序使用的所有程序集:
dir /s /b *.dll
然后,我们需要使用 NGen.exe 生成本机代码。 在 Visual Studio 命令提示符下运行以下命令:
ngen install assembly_name
这将生成一些本机代码,并将其缓存。此后,每次我们使用应用程序时,启动时间就会更短,执行速度也会更快。
GC 改进
.NET 分代垃圾回收(Garbage Collection,以下简称 GC)是一种自动管理内存分配和回收的应用程序技术。CLR 实现了自动 GC,为应用程序提供了强大的内存管理功能。但是,GC 也需要时间和资源来完成其工作。如果您的应用程序执行频繁会创建大量的对象,那么 GC 的工作会变得更加复杂。在这种情况下,我们需要对 GC 进行一些改进。
在 .NET 应用程序中,GC 的基本行为由 GC 类型(例如服务器/工作站)和相应的配置文件(例如 app.config)来决定。我们可以通过修改配置文件来改变 GC 行为。
下面是一些改进 GC 性能的示例:
减少频繁 GC 引起的管理开销
使用单个全局对象创建频繁的小对象,会产生过多的垃圾,这可能会导致 GC 频繁调用,进而影响性能。解决这个问题的方法是使用对象池技术,它通过缓存池来存储常用对象,从而减少频繁 GC 引起的管理开销。
以下是一个对象池示例:
-- -------------------- ---- ------- ------ ----- ------------- ----- - - ----- - ------- -------- ---------------- --------- ------- -------- ------- ----------------- ------ ------------------ ---------------- - ---------------- - --------------- -- ----- --- ----------------------------------------------- -------- - --- ------------------- - ------ - ----------- -- -------------------- - ----- - ---- - ------------------- ------ ---- ----------- ----- -- ------------------- -
使用对象池获取和添加实例对象的语法:
var pool = new ObjectPool<MyClassName>(() => new MyClassName()); var myObj = pool.GetObject(); ... pool.PutObject(myObj);
避免 Gen2 垃圾
一些高级应用程序会在应用程序运行期间创建大量的临时对象,这些对象的寿命很短。避免这些对象成为 Gen2 垃圾(一个处于最高垃圾等级的对象),可以减少 GC 频率。
我们可以使用以下技术来避免 Gen2 垃圾。
对于不可变的值类型(例如 int, char),使用 readonly 来声明,以避免更新。
对于可变值类型(例如 List、Dictionary),使用 Clear() 方法来避免产生垃圾。
对于可变值类型,尽可能使用 AddRange() 和 RemoveRange() 来以原地方式修改集合。
避免使用无效或空对象,因为空对象也会拖慢性能和导致垃圾存在。
JIT 编译器优化
JIT 编译器会将 C# 代码转换成本地机器代码,并快速生成可执行代码。但它的性能优化取决于 CLR。因此,我们需要了解 CLR 的工作原理,以最大限度地提高 JIT 编译器的性能。
以下是几个 JIT 编译器优化的示例:
分发虚拟调用
虚拟调用是一种多态方法,可以通过基类引用调用子类功能。由于虚拟调用会带来性能开销,.NET CLR 会尝试避免这种情况。CLR 会跟踪类型最后一次使用的所有虚拟调用,并将它们缓存以增加性能。
如果我们需要将虚拟调用性能提高到最优水平,我们可以考虑使用 final 关键字来避免虚拟调用,或使用结构体类型,从而避免虚拟调用的必要性。
inline 展开方法
Inline 展开方法是指将小的或简单的方法内联到调用程序中。这种技术可以减少函数调用的开销,从而提高应用程序的性能。
在 C# 中,使用 inline 关键字可以告诉 JIT 编译器在编译时对方法进行内联。它可以使 CLR 快速生成可执行的本机代码。然而,inline 关键字依赖于 JIT 编译器。并不是所有的情况都适用于 inline 关键字,这取决于方法的复杂性和大小。
在下面的示例中,我们将方法 inline 到调用函数中:
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static int AddNumbers(int a, int b) { return a + b; }
在性能和尺寸之间取得平衡时,可以通过在代码中添加一些行义数据(Attribte)来手动执行内联操作。你可以使用以下代码来完成此任务。
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void DoSomething() { // code here... }
总体结论
CLR 调优是提高 .NET 应用程序性能的重要部分。通过预编译代码、改进 GC 行为和使用 JIT 编译器的优化方法,我们可以有效地提高应用程序性能。
代码优化
在执行 CLR 调优之后,我们还能对代码本身进行优化。以下是一些对代码进行优化的技巧。
避免不必要的装箱和拆箱操作
.NET 框架支持值类型和引用类型。值类型是结构体和基本类型,它们存储在堆栈上,而不是存储在堆上。而引用类型是类、接口和委托,它们存储在堆上。
在 C# 中,可以使用 object 类型来实现多态的特性,但也会导致许多装箱(把值类型转换成具有相同值的引用类型)和拆箱(把引用类型转换成值类型)操作。装箱操作可以导致性能下降,特别是在循环中多次执行时。因此,我们应该尽可能避免不必要的装箱和拆箱操作。
在下面的代码中,我们将避免不必要的装箱操作。
错误示例:
-- -------------------- ---- ------- --------- ------- - --- ------------ --- ---- - - -- - - ---- ---- - --------------- - ------- ------- ------ -- -------- - ------------------------------------- -
改进建议示例:
-- -------------------- ---- ------- --------- ------- - --- ------------ --- ---- - - -- - - ---- ---- - --------------- - ------- ---- ------ -- -------- - ------------------------------------- -
避免重复计算
重复计算也会导致性能下降。所以,我们在编写代码时应该避免多次对同一个值进行计算。这种情况可以通过使用缓存来解决,缓存可以减少重复计算,从而提高程序的性能。
在下面的示例代码中,我们将采用缓存来减少不必要的计算。
错误示例:
-- -------------------- ---- ------- ------ --- -------------- -- --- -- - ------ - - - - -------- - ------ ------ ------- - ------ -------- -
改进建议示例:
-- -------------------- ---- ------- ------- ------ ---- ------ --- -------------- -- --- -- - ------ - - - - -------- - ------ ------ ------- - -- ---- -- -- - --- - -------- - ------ ---- -
一般建议
最后,以下是一些通用的建议。
尽可能使用基元类型
尽可能使用基元类型可以减少装箱和拆箱操作,从而提高程序的性能。使用基元类型可以使程序更快而不增加太多的开销。
限制为只读/只写/封闭
只读/只写/封闭(Read-only/Write-only/Encapsulate)是面向对象编程的三个基本原则。遵循这些原则可以确保程序的一致性和可维护性,并确保代码的正确性。
删除无用代码
可以通过使用一些工具,如 Visual Studio 代码分析器,来自动检测无用代码。删除无用代码可以释放内存和资源,从而提高程序性能。
总体结论
代码优化是提高 .NET 应用程序性能的重要部分。我们可以通过避免不必要的装箱和拆箱、避免重复计算和采用一些通用的建议来优化代码,以确保程序的性能最佳。
结论
C#/.NET 应用程序的性能优化是一个涉及 CLR 调优和代码优化的过程。通过使用预编译程序集、GC 改进和 JIT 编译器优化等方法,我们可以调整 CLR 的性能。而通过避免不必要的装箱和拆箱、避免重复计算和采用一些通用的建议,我们可以优化代码的性能。
无论是 CLR 调优还是代码优化,都需要经验和实践。通过这篇文章中的技术和示例代码,您可以更好地理解 C#/.NET 应用程序性能优化,并逐步提高您的编程技能。我希望这对您很有帮助,欢迎补充学习和实践。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/677368536d66e0f9aae2ef48