提高.NET应用性能
提高.NET应用性能的方法
写在前面
设计良好的系统,除了架构层面的优良设计外,剩下的大部分就在于如何设计良好的代码,.NET提供了很多的类型,这些类型非常灵活,也非常好用,比如List,Dictionary、HashSet、StringBuilder、string等等。在大多数情况下,大家都是看着业务需要直接去用,似乎并没有什么问题。从我的实际经验来看,出现问题的情况确实是少之又少。之前有朋友问我,我有没有遇到过内存泄漏的情况,我说我写的系统没有,但是同事写的我遇到过几次。
为了记录曾经发生的问题,也为了以后可以避免类似的问题,总结这篇文章,力图从数据统计角度总结几个有效提升.NET性能的方法。
本文基于.NET Core 3.0 Preview4,采用[Benchmark]进行测试,如果不了解Benchmark,建议了解完之后再看本文。
集合-隐藏的初始容量及自动扩容
在.NET里,List、Dictionary、HashSet这些集合类型都具有初始容量,当新增的数据大于初始容量时,会自动扩展,可能大家在使用的时候很少注意这个隐藏的细节(此处暂不考虑默认初始容量、加载因子、扩容增量)。
自动扩容给使用者的感知是无限容量,如果用的不是很好,可能会带来一些新的问题。因为每当集合新增的数据大于当前已经申请的容量的时候,会再申请更大的内存容量,一般是当前容量的两倍。这就意味着我们在集合操作过程中可能需要额外的内存开销。
在本次测试中,我用到了四种场景,可能并不是很完全,但是很有说明性,每个方法都是循环了1000次,时间复杂度均为O(1000):
- DynamicCapacity:不设置默认长度
- LargeFixedCapacity:默认长度为2000
- FixedCapacity:默认长度为1000
- FixedAndDynamicCapacity:默认长度为100
下图为List的测试结果,可以看到其综合性能排名是FixedCapacity>LargeFixedCapacity>DynamicCapacity>FixedAndDynamicCapacity

下图为Dictionary的测试结果,可以看到其综合性能排名是FixedCapacity>LargeFixedCapacity>FixedAndDynamicCapacity>DynamicCapacity,在Dictionary场景中,FixedAndDynamicCapacity和DynamicCapacity的两个方法性能相差并不大,可能是量还不够大

下图为HashSet的测试结果,可以看到其综合性能排名是FixedCapacity>LargeFixedCapacity>FixedAndDynamicCapacity>DynamicCapacity,在HashSet场景中,FixedAndDynamicCapacity和DynamicCapacity的两个方法性能相差还是很大的

综上所述:
一个恰当的容量初始值,可以有效提升集合操作的效率,如果不太好设置一个准确的数据,可以申请比实际稍大的空间,但是会浪费内存空间,并在实际上降低集合操作性能,编程的时候需要特别注意。
以下是List的测试源码,另两种类型的测试代码与之基本一致:
1: public class ListTest
2: {
3: private int size = 1000;
4:
5: [Benchmark]
6: public void DynamicCapacity()
7: {
8: List<int> list = new List<int>();
9: for (int i = 0; i < size; i++)
10: {
11: list.Add(i);
12: }
13: }
14:
15: [Benchmark]
16: public void LargeFixedCapacity()
17: {
18: List<int> list = new List<int>(2000);
19: for (int i = 0; i < size; i++)
20: {
21: list.Add(i);
22: }
23: }
24:
25: [Benchmark]
26: public void FixedCapacity()
27: {
28: List<int> list = new List<int>(size);
29: for (int i = 0; i < size; i++)
30: {
31: list.Add(i);
32: }
33: }
34:
35: [Benchmark]
36: public void FixedAndDynamicCapacity()
37: {
38: List<int> list = new List<int>(100);
39: for (int i = 0; i < size; i++)
40: {
41: list.Add(i);
42: }
43: }
44: }
结构体与类
结构体是值类型,引用类型和值类型之间的区别是引用类型在堆上分配并进行垃圾回收,而值类型在堆栈中分配并在堆栈展开时被释放,或内联包含类型并在它们的包含类型被释放时被释放。 因此,值类型的分配和释放通常比引用类型的分配和释放开销更低。
一般来说,框架中的大多数类型应该是类。 但是,在某些情况下,值类型的特征使得其更适合使用结构。
如果类型的实例比较小并且通常生存期较短或者通常嵌入在其他对象中,则定义结构而不是类。
该类型具有所有以下特征,可以定义一个结构:
它逻辑上表示单个值,类似于基元类型(
int,double,等等)它的实例大小小于 16 字节
它是不可变的
它不会频繁装箱
在所有其他情况下,应将类型定义为类。由于结构体在传递的时候,会被复制,因此在某些场景下可能并不适合提升性能。
以上摘自MSDN,可点击查看详情

可以看到Struct的平均分配时间只有Class的六分之一。
以下为该案例的测试源码:
1: public struct UserStructTest
2: {
3: public int UserId { get;set; }
4:
5: public int Age { get; set; }
6: }
7:
8: public class UserClassTest
9: {
10: public int UserId { get; set; }
11:
12: public int Age { get; set; }
13: }
14:
15: public class StructTest
16: {
17: private int size = 1000;
18:
19: [Benchmark]
20: public void TestByStruct()
21: {
22: UserStructTest[] test = new UserStructTest[this.size];
23: for (int i = 0; i < size; i++)
24: {
25: test[i].UserId = 1;
26: test[i].Age = 22;
27: }
28: }
29:
30: [Benchmark]
31: public void TestByClass()
32: {
33: UserClassTest[] test = new UserClassTest[this.size];
34: for (int i = 0; i < size; i++)
35: {
36: test[i] = new UserClassTest
37: {
38: UserId = 1,
39: Age = 22
40: };
41: }
42: }
43: }
StringBuilder与string
字符串是不可变的,每次的赋值都会重新分配一个对象,当有大量字符串操作时,使用string非常容易出现内存溢出,比如导出Excel操作,所以大量字符串的操作一般推荐使用StringBuilder,以提高系统性能。
以下为一千次执行的测试结果,可以看到StringBuilder对象的内存分配效率十分的高,当然这是在大量字符串处理的情况,少部分的字符串操作依然可以使用string,其性能损耗可以忽略

这是执行五次的情况,可以发现虽然string的内存分配时间依然较长,但是稳定且错误率低

测试代码如下:
1: public class StringBuilderTest
2: {
3: private int size = 5;
4:
5: [Benchmark]
6: public void TestByString()
7: {
8: string s = string.Empty;
9: for (int i = 0; i < size; i++)
10: {
11: s += "a";
12: s += "b";
13: }
14: }
15:
16: [Benchmark]
17: public void TestByStringBuilder()
18: {
19: StringBuilder sb = new StringBuilder();
20: for (int i = 0; i < size; i++)
21: {
22: sb.Append("a");
23: sb.Append("b");
24: }
25:
26: string s = sb.ToString();
27: }
28: }
析构函数
析构函数标识了一个类的生命周期已调用完毕时,会自动清理对象所占用的资源。析构方法不带任何参数,它实际上是保证在程序中会调用垃圾回收方法 Finalize(),使用析构函数的对象不会在G0中处理,这就意味着该对象的回收可能会比较慢。通常情况下,不建议使用析构函数,更推荐使用IDispose,而且IDispose具有刚好的通用性,可以处理托管资源和非托管资源。
以下为本次测试的结果,可以看到内存平均分配效率的差距还是很大的

测试代码如下:
1: public class DestructionTest
2: {
3: private int size = 5;
4:
5: [Benchmark]
6: public void NoDestruction()
7: {
8: for (int i = 0; i < this.size; i++)
9: {
10: UserTest userTest = new UserTest();
11: }
12: }
13:
14: [Benchmark]
15: public void Destruction()
16: {
17: for (int i = 0; i < this.size; i++)
18: {
19: UserDestructionTest userTest = new UserDestructionTest();
20: }
21: }
22: }
23:
24: public class UserTest: IDisposable
25: {
26: public int UserId { get; set; }
27:
28: public int Age { get; set; }
29:
30: public void Dispose()
31: {
32: Console.WriteLine("11");
33: }
34: }
35:
36: public class UserDestructionTest
37: {
38: ~UserDestructionTest()
39: {
40:
41: }
42:
43: public int UserId { get; set; }
44:
45: public int Age { get; set; }
46: }
提高.NET应用性能的更多相关文章
- 提高ASP.net性能的十种方法
提高ASP.net性能的十种方法 2014-10-24 空城66 摘自 博客园 阅 67 转 1 转藏到我的图书馆 微信分享: 今天无意中看了一篇关于提高ASP.NET性能的文章,个人 ...
- 25条提高iOS App性能的建议和技巧
这篇文章来自iOS Tutorial Team 成员 Marcelo Fabri, 他是 Movile 的一个iOS开发者. Check out his personal website or fol ...
- 提高 DHTML 页面性能
联盟电脑摘要:本文说明了某些DHTML功能对性能的重大影响,并提供了一些提高DHTML页面性能的技巧. 目录 简介 成批处理DHTML更改 使用innerText 使用DOM添加单个元素 扩展SELE ...
- 25条提高iOS app性能的方法和技巧
以下这些技巧分为三个不同那个的级别---基础,中级,高级. 基础 这些技巧你要总是想着实现在你开发的App中. 1. 用ARC去管理内存(Use ARC to Manage Memory) 2.适当的 ...
- 用 Function.apply() 的参数数组化来提高 JavaScript程序性能
我们再来聊聊Function.apply() 在提升程序性能方面的技巧. 我们先从 Math.max() 函数说起, Math.max后面可以接任意个参数,最后返回所有参数中的最大值. 比如 aler ...
- 如何提高jQuery的性能
缓存变量DOM遍历是昂贵的,所以尽量将会重用的元素缓存. // 糟糕 h = $('#element').height(); $('#element').css('height',h-20); // ...
- 一个用于每一天JavaScript示例-使用缓存计算(memoization)为了提高应用程序性能
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- 如何提高windows的性能
默认windows启用了很多的效果,我们可能平时没有注意到,比如什么淡入淡出效果之类的,其实在我看来,这些效果不仅难看,而且影响了windows的性能,下面我就来说说怎么通过关闭这些效果来提高wind ...
- 提高磁盘访问性能 - NtfsDisableLastAccessUpdate
这个技巧可以提高磁盘访问性能,不过仅适用于NTFS文件系统. 我们知道,当在磁盘管理应用程序中列出目录结构时──效果类似“资源管理器”.“文件管理 器”(Windows NT 3.xx/4.0下的称 ...
- 怎样写SQL语句可以提高数据库的性能
1.首先要搞明白什么叫执行计划? 执行计划是数据库根据SQL语句和相关表的统计信息作出的一个查询方案,这个方案是由查询优化器自动分析产生的,比如一条SQL语句如果用来从一个10万条记录的表中查1条记录 ...
随机推荐
- How To Use The Repository Pattern In Laravel
The Repository Pattern in Laravel is a very useful pattern with a couple of great uses. The first us ...
- 为什么final类型map和list内容可以修改
URL地址:https://blog.csdn.net/AlbertFly/article/details/76855367
- vue文件夹上传源码
一. 功能性需求与非功能性需求 要求操作便利,一次选择多个文件和文件夹进行上传:支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传.刷新页面后继续传输. ...
- luogu 5505 [JSOI2011]分特产 广义容斥
共有 $m$ 种物品,每个物品 $a[i]$ 个,分给 $n$ 个人,每个人至少要拿到一件,求方案数. 令 $f[i]$ 表示钦定 $i$ 个没分到特产,其余 $(n-i)$ 个人随便选的方案数,$g ...
- int(“1.6”),int(1.6)输出结果?
>>> int("1.6") Traceback (most recent call last): File , in <module> ValueE ...
- 在本机上用IO流实现复制粘贴功能
/** * 复制文件夹 * @param sourcePath * @param targetPath * @throws IOException */ public void copyFolder( ...
- Linux中的MySQL授权远程连接
Linux中 MySQL 授权远程连接 参考地址:https://www.centos.bz/2018/10/linux%e4%b8%ad-mysql-%e6%8e%88%e6%9d%83%e8%bf ...
- Tkinter 之Frame标签
一.参数说明 语法 作用 width 设置 Frame 的宽度默认值是 0 height 设置 Frame 的高度默认值是 0 background(bg) 设置 Frame 组件的背景颜色 bord ...
- Spring家族主流成员介绍
摘 要:Spring 就像一个大家族,有众多衍生产品例如 Boot,Security,JPA等等.但他们的基础都是Spring 的 IOC 和 AOP,IOC提供了依赖注入的容器,而AOP解决了面向切 ...
- Nginx之web服务器
Nginx的介绍 Nginx是由俄罗斯的Igor Sysoev使用C语言开发的轻量级.高性能.开源.跨平台的Web服务器. Nginx使用基于事件驱动的架构能够并发处理百万级的TCP连接,高模块化的设 ...