Several well-structured design methods to improve the performance of .NET applications

EDITORIAL

A well-designed system, in addition to excellent design architecture level, but most of the rest is how well-designed code, .NET offers many types, which is very flexible, very easy to use, such as List, Dictionary, HashSet , StringBuilder, string, and so on. In most cases, we are looking at the need to go directly to the business use, does not seem a problem. From my experience, the situation is indeed very few problems. A friend asked me before, I have not encountered a memory leak case, I said I did not write the system, but I met my colleagues wrote a few times.

To record the problems have occurred, but also to avoid similar problems in the future, this article summarizes an attempt to summarize data from a few statistically valid way to improve the performance of .NET.

Based on .NET Core 3.0 Preview4, using [Benchmark] test, if not understand Benchmark, look at this article recommended after complete understanding.

Collections - Hidden initial capacity and automatic expansion

In .NET, List, Dictionary, HashSet collection of these types have an initial capacity when new data is greater than the initial capacity, will be automatically extended, in time we may use a little attention to this hidden details ( here temporarily consider default initial capacity, load factor, incremental expansion ).

Automatic expansion to the user's perception of infinite capacity, if not very good, may bring new problems. Because every time a set of new data has been greater than the capacity of the current application time, will re-apply for greater memory capacity, generally twice the current capacity. This means that we may require additional memory overhead during the collection operation.

In this test, I used the four scenarios, it may not completely, but very illustrative, each method is the 1000 cycle, the time complexity are O (1000):

  • DynamicCapacity: not set the default length
  • LargeFixedCapacity: The default length of 2000
  • FixedCapacity: The default length of 1000
  • FixedAndDynamicCapacity: default length is 100

List below shows the test results, you can see its overall performance ranking is FixedCapacity> LargeFixedCapacity> DynamicCapacity> FixedAndDynamicCapacity

list

The following figure shows Dictionary test results, you can see its overall performance ranking is FixedCapacity> LargeFixedCapacity> FixedAndDynamicCapacity> DynamicCapacity, in Dictionary scene, the two methods and performance FixedAndDynamicCapacity DynamicCapacity the difference is not large, probably the amount is not big enough

Dec

HashSet below shows the test results, you can see its overall performance ranking is FixedCapacity> LargeFixedCapacity> FixedAndDynamicCapacity> DynamicCapacity, in HashSet scene, the two methods and performance FixedAndDynamicCapacity DynamicCapacity difference is still very large

hashset

In summary:

An appropriate initial value capacity, can effectively enhance the efficiency of the set of operations, if a very good and accurate data set may apply slightly larger than the actual space, but wastes memory space, and actually reduces the performance of set operations, programming when the need for special attention.

The following is a List of test source code, and the other two types of test code with basically the same:

   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:  }

结构体与类

结构体是值类型,引用类型和值类型之间的区别是引用类型在堆上分配并进行垃圾回收,而值类型在堆栈中分配并在堆栈展开时被释放,或内联包含类型并在它们的包含类型被释放时被释放。 因此,值类型的分配和释放通常比引用类型的分配和释放开销更低。

一般来说,框架中的大多数类型应该是类。 但是,在某些情况下,值类型的特征使得其更适合使用结构。

如果类型的实例比较小并且通常生存期较短或者通常嵌入在其他对象中,则定义结构而不是类。

该类型具有所有以下特征,可以定义一个结构:

  • 它逻辑上表示单个值,类似于基元类型(intdouble,等等)

  • 它的实例大小小于 16 字节

  • 它是不可变的

  • 它不会频繁装箱

在所有其他情况下,应将类型定义为类。由于结构体在传递的时候,会被复制,因此在某些场景下可能并不适合提升性能。

以上摘自MSDN,可点击查看详情

struct

可以看到Struct的平均分配时间是Class的6倍。

以下为该案例的测试源码:

   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,其性能损耗可以忽略

image

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

image

测试代码如下:

   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具有刚好的通用性,可以处理托管资源和非托管资源。

以下为本次测试的结果:

of the

测试代码如下:

   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:  }

Guess you like

Origin www.cnblogs.com/edison0621/p/11069653.html