提高.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条记录 ... 
随机推荐
- 最短路--SPFA及其优化
			SPFA Shortest Path Faster Algorithm 最短路径最快算法 算法思想 SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路 ... 
- 洛谷 P2822 组合数问题 题解
			今天又考试了...... 这是T2. Analysis 考试时想了一个判断质因数个数+打表的神奇方法,但没在每次输入n,m时把ans置0,50分滚粗. 看了题解才发现原来是杨辉三角+二维前缀和,果然还 ... 
- PostgreSQL 时间函数 extract函数
			计算时间差天数 select extract(day FROM (age('2017-12-10'::date , '2017-12-01'::date))); 计算时间差秒数 select ex ... 
- [Luogu] 外星密码
			https://www.luogu.org/problemnew/show/P1928 沙比提 读清题目 #include <bits/stdc++.h> using namespace ... 
- 主席树K-th Number
			/*K-th NumberTime Limit: 20000MS Memory Limit: 65536KTotal Submissions: 44535 Accepted: 14779Case Ti ... 
- php des 对称加解密类
			<?php header("Content-Type: text/html;charset=utf-8"); /** * des 对称加解密 */ class des { p ... 
- Spring Cloud Gateway(五):路由定位器 RouteLocator
			本文基于 spring cloud gateway 2.0.1 1.简介 直接 获取 路 由 的 方法 是 通过 RouteLocator 接口 获取. 同样, 该 顶 级 接口 有多 个 实现 类, ... 
- 【原】Python基础-序列
			1 序列 在Python中,最基本的数据结构是序列,序列中每个元素被分配一个编号,也称为索引.第一个索引为0,第二个则是1,以此类推.序列中最后一个元素被标为-1,倒数第二个元素被标为-2,以此类推. ... 
- 粒子群优化算法及其java实现
			憋了两周终于把开题报告憋出来了,再一次证明自己不适合搞学术,哎--,花了点时间把报告中提到的粒子群算法看了看,看了些资料,用java跑起来. 算法简介 粒子群算法最先由Barnhart博士和Kenne ... 
- nginx -- 设置单点登录 sso oidc oauth
			这个开源项目可以找到你想要的: https://github.com/vouch/vouch-proxy 
