EDITORIAL
優れたデザインアーキテクチャレベルに加えて、システムをうまく設計されたが、残りのほとんどは、よく設計されたコードは、.NETは、このようなリスト、辞書、HashSetのように、非常に使いやすい、非常に柔軟性のある、多くの種類を提供しています方法です、StringBuilderの、文字列、およびように。ほとんどの場合、我々はビジネスユースに直接移動する必要性を見ている、問題は思えません。私の経験から、状況は確かに非常にいくつかの問題です。友人が私はメモリリークのケースに遭遇していないが、前に私に尋ねた、私は、システムを書いていないと述べたが、私は私の同僚が数回を書いた会いました。
問題が発生した記録するには、だけでなく、将来的には同様の問題を回避するために、この記事では、.NETのパフォーマンスを向上させるためにいくつかの統計的に有効な方法でデータを要約する試みをまとめたもの。
.NETのコア3.0 Preview4に基づいて、使用して[ベンチマーク]テストでは、ベンチマークを理解していない場合は、完全に理解した後、推奨この記事を見てください。
コレクション - 隠された初期容量と自動拡張
新しいデータが初期容量よりも大きい場合、.NETでは、これらのタイプのリスト、辞書、HashSetのコレクションが自動的に延長される、初期容量を持つ、時間に我々は(この隠された内容にはほとんど注意を使用して、一時的にここに初期容量、負荷係数、増分展開をデフォルト考えます)。
非常に良いではない場合には、無限の容量の利用者の認知への自動拡張は、新たな問題をもたらす可能性があります。毎回新しいデータのセットは、通電時間の容量よりも大きくなっているので、より大きなメモリ容量、一般的に二倍の電流容量のために再適用されます。これは、我々が収集操作中に追加のメモリオーバーヘッドを必要とするかもしれないことを意味します。
この試験では、私はそれが完全ではないかもしれないが、非常に例示の、4つのシナリオを用い、各メソッド1000サイクルである、時間計算量はO(1000)です。
- DynamicCapacity:デフォルトの長さを設定していません
- LargeFixedCapacity:2000のデフォルトの長さ
- FixedCapacity:1000のデフォルトの長さ
- FixedAndDynamicCapacity:デフォルトの長さは100です
以下のリストは、あなたが見ることができ、その全体的なパフォーマンスのランキングですFixedCapacity> LargeFixedCapacity> DynamicCapacity> FixedAndDynamicCapacity試験結果を示します
次の図は、辞書テストの結果を示して、あなたはその全体的なパフォーマンスのランキングですFixedCapacity> LargeFixedCapacity> FixedAndDynamicCapacity>辞書シーンでDynamicCapacity、2つの方法と性能FixedAndDynamicCapacity DynamicCapacityを見ることができるの違いは、おそらく量が十分でない、大きさではありません
以下HashSetのは、テストの結果を示して、あなたはその全体的なパフォーマンスの順位がFixedCapacity> LargeFixedCapacity> FixedAndDynamicCapacity> DynamicCapacityで見ることができ、HashSetのシーンでは、二つの方法およびパフォーマンスFixedAndDynamicCapacity DynamicCapacity違いは依然として非常に大きいです
要約すると:
適切な初期値の容量、効率的に非常に良好であり、正確なデータセット場合、実際の空間よりもわずかに大きい適用することができる、操作のセットの効率を向上させるが、浪費メモリ空間、及び実際に集合演算の性能を低下させることができ、プログラミングとき、特別な注意が必要。
下記は、テストのソースコードのリストであり、基本的に同じでテストコードの他の二つのタイプ:
1: パブリック クラス ListTest
2: {
3: プライベート int型のサイズ= 1000;
4:
5: [ベンチマーク]
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的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,其性能损耗可以忽略
这是执行五次的情况,可以发现虽然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: }