提高.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条记录 ...
随机推荐
- oracle存储过程把查询到的值更新到别的表
create or replace procedure update_nst_t_Clime2 as cursor c_db is select * from NST_T_FRAME f ,) as ...
- 洛谷 P3956 棋盘 题解
每日一题 day5 打卡 Analysis 深搜+剪枝+瞎jb判断 1.越界 2.这个点无色 3.当前的价值已经比答案大 三种情况要剪枝 我搜索里判断要不要施法的时候没判断上一次有没有施法,白调了0. ...
- MongoDB CRUD 操作
crud是指在做计算处理时的增加(Create).读取查询(Retrieve).更新(Update)和删除(Delete)几个单词的首字母简写.crud主要被用在描述软件系统中数据库或者持久层的基本操 ...
- Greenplum 监控segment是否正常
在greenplum运行过程中,Segement很有可能因为压力大出现不可用的情况, 主备Segement发现了切换,或是主备Segement网络断开,数据不同步了.在 默认情况下,如果GreenPl ...
- Elasticsearch环境搭建和介绍(Windows)
一.Elasticsearch介绍和安装 1.1 介绍 Elastic Elastic官网:https://www.elastic.co/cn/ Elastic有一条完整的产品线:Elasticse ...
- 贾扬清牛人(zz)
贾扬清加入阿里巴巴后,能否诞生出他的第三个世界级杰作? 文 / 华商韬略 张凌云 本文转载,著作权归原作者所有 贾扬清加入阿里巴巴后,能否诞生出他的第三个世界级杰作? 2017年1月11日,美国 ...
- 决策单调性优化DP学习笔记
用途 废话,当然是在DP式子满足某些性质的时候来优化复杂度-- 定义 对于\(j\)往大于\(j\)的\(i\)转移,可以表示成一个关于\(i\)的函数\(f_j(i)\),也就是\(dp_i=\ma ...
- Codeforces - 2019年11月补题汇总
大概目标是补到 #500 为止的 Div. 2 ,先定个小目标,寒假开始前补到 #560 为止 Codeforces Round #599 (Div. 2) 5/6 备注:0-1BFS(补图连通块) ...
- journalnode Can't scan a pre-transactional edit log 异常处理
由于数据磁盘爆满,达到100%,导致journalnode宕掉,在启动journalnode以后,查看日志,提示Can't scan a pre-transactional edit log,这个时候 ...
- KCF追踪方法流程原理
读"J. F. Henriques, R. Caseiro, P. Martins, J. Batista, 'High-speed tracking with kernelized cor ...