本文通過ANTS Memory Profiler工具探索c#中+、string.Concat、string.Format、StringBuilder.Append四種方式進行字元串拼接時的性能。 本文涉及程式為.NET Core 2.0控制台應用程式。 一、常量字元串拼接 private stati ...
本文通過ANTS Memory Profiler工具探索c#中+、string.Concat、string.Format、StringBuilder.Append四種方式進行字元串拼接時的性能。
本文涉及程式為.NET Core 2.0控制台應用程式。
一、常量字元串拼接
private static void TestPerformance(Action action, int times) { Stopwatch sw = new Stopwatch(); sw.Start(); for(int i = 0; i < times; i++) { action(); } sw.Stop(); Console.WriteLine(action.Method.Name + ", FullTime: " + sw.ElapsedMilliseconds); }常量字元串測試方法
1.+方法
1.1連續拼接
private static void AddTest() { string s = string.Empty; s = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8"; }+連續拼接常量字元串
運行時間:
記憶體情況:
IL代碼:
.method private hidebysig static void AddTest() cil managed { // Code size 7 (0x7) .maxstack 8 IL_0000: ldsfld string [System.Runtime]System.String::Empty IL_0005: pop IL_0006: ret } // end of method Program::AddTest
1.2分段拼接
private static void AddWithParamTest2() { string s = string.Empty; s += "1"; s += "2"; s += "3"; s += "4"; s += "5"; s += "6"; s += "7"; s += "8"; }分段拼接常量字元串
運行時間:
記憶體情況:
IL代碼:
.method private hidebysig static void AddWithParamTest2() cil managed { // Code size 87 (0x57) .maxstack 2 IL_0000: ldsfld string [System.Runtime]System.String::Empty IL_0005: ldstr "1" IL_000a: call string [System.Runtime]System.String::Concat(string, string) IL_000f: ldstr "2" IL_0014: call string [System.Runtime]System.String::Concat(string, string) IL_0019: ldstr "3" IL_001e: call string [System.Runtime]System.String::Concat(string, string) IL_0023: ldstr "4" IL_0028: call string [System.Runtime]System.String::Concat(string, string) IL_002d: ldstr "5" IL_0032: call string [System.Runtime]System.String::Concat(string, string) IL_0037: ldstr "6" IL_003c: call string [System.Runtime]System.String::Concat(string, string) IL_0041: ldstr "7" IL_0046: call string [System.Runtime]System.String::Concat(string, string) IL_004b: ldstr "8" IL_0050: call string [System.Runtime]System.String::Concat(string, string) IL_0055: pop IL_0056: ret } // end of method Program::AddWithParamTest2
通過IL代碼可以看出,分段的+=代碼調用的是Concat方法,並且比連續的+多開闢了許多記憶體空間。
2.Concat方法
2.1分次Concat
private static void ConcatTest() { string s = string.Empty; s = string.Concat(s, "1"); s = string.Concat(s, "2"); s = string.Concat(s, "3"); s = string.Concat(s, "4"); s = string.Concat(s, "5"); s = string.Concat(s, "6"); s = string.Concat(s, "7"); s = string.Concat(s, "8"); }分次Concat常量字元串
IL代碼:
.method private hidebysig static void AddWithParamTest2() cil managed { // Code size 87 (0x57) .maxstack 2 IL_0000: ldsfld string [System.Runtime]System.String::Empty IL_0005: ldstr "1" IL_000a: call string [System.Runtime]System.String::Concat(string, string) IL_000f: ldstr "2" IL_0014: call string [System.Runtime]System.String::Concat(string, string) IL_0019: ldstr "3" IL_001e: call string [System.Runtime]System.String::Concat(string, string) IL_0023: ldstr "4" IL_0028: call string [System.Runtime]System.String::Concat(string, string) IL_002d: ldstr "5" IL_0032: call string [System.Runtime]System.String::Concat(string, string) IL_0037: ldstr "6" IL_003c: call string [System.Runtime]System.String::Concat(string, string) IL_0041: ldstr "7" IL_0046: call string [System.Runtime]System.String::Concat(string, string) IL_004b: ldstr "8" IL_0050: call string [System.Runtime]System.String::Concat(string, string) IL_0055: pop IL_0056: ret } // end of method Program::AddWithParamTest2
可見IL代碼與+分段拼接常量字元串相同,性能相似。
2.2Concat一次拼接常量字元串
private static void ConcatTest2() { string s = string.Empty; string.Concat(s, "1", "2", "3", "4", "5", "6", "7", "8"); }Concat一次拼接常量字元串
運行時間:
記憶體情況:
IL代碼:
.method private hidebysig static void ConcatTest2() cil managed { // Code size 88 (0x58) .maxstack 4 .locals init (string V_0) IL_0000: ldsfld string [System.Runtime]System.String::Empty IL_0005: stloc.0 IL_0006: ldc.i4.s 9 IL_0008: newarr [System.Runtime]System.String IL_000d: dup IL_000e: ldc.i4.0 IL_000f: ldloc.0 IL_0010: stelem.ref IL_0011: dup IL_0012: ldc.i4.1 IL_0013: ldstr "1" IL_0018: stelem.ref IL_0019: dup IL_001a: ldc.i4.2 IL_001b: ldstr "2" IL_0020: stelem.ref IL_0021: dup IL_0022: ldc.i4.3 IL_0023: ldstr "3" IL_0028: stelem.ref IL_0029: dup IL_002a: ldc.i4.4 IL_002b: ldstr "4" IL_0030: stelem.ref IL_0031: dup IL_0032: ldc.i4.5 IL_0033: ldstr "5" IL_0038: stelem.ref IL_0039: dup IL_003a: ldc.i4.6 IL_003b: ldstr "6" IL_0040: stelem.ref IL_0041: dup IL_0042: ldc.i4.7 IL_0043: ldstr "7" IL_0048: stelem.ref IL_0049: dup IL_004a: ldc.i4.8 IL_004b: ldstr "8" IL_0050: stelem.ref IL_0051: call string [System.Runtime]System.String::Concat(string[])
IL_0056: stloc.0
IL_0057: ret
} // end of method Program::ConcatTest2
通過IL代碼可以看出,string.Concat(s, s1, s2, s3)並不調用String.Concat方法,而是直接在堆棧上進行操作。因為需要局部變數來標記,比分次調用Concat方法所占的記憶體要多一些。
3.Format方法
3.1一次Format
private static void FormatTest() { string s = string.Empty; s = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}", "1", "2", "3", "4", "5", "6", "7", "8"); }Format常量字元串拼接
運行時間:
記憶體情況:
IL代碼:
.method private hidebysig static void FormatTest() cil managed { // Code size 88 (0x58) .maxstack 5 IL_0000: ldsfld string [System.Runtime]System.String::Empty IL_0005: pop IL_0006: ldstr "{0}{1}{2}{3}{4}{5}{6}{7}" IL_000b: ldc.i4.8 IL_000c: newarr [System.Runtime]System.Object IL_0011: dup IL_0012: ldc.i4.0 IL_0013: ldstr "1" IL_0018: stelem.ref IL_0019: dup IL_001a: ldc.i4.1 IL_001b: ldstr "2" IL_0020: stelem.ref IL_0021: dup IL_0022: ldc.i4.2 IL_0023: ldstr "3" IL_0028: stelem.ref IL_0029: dup IL_002a: ldc.i4.3 IL_002b: ldstr "4" IL_0030: stelem.ref IL_0031: dup IL_0032: ldc.i4.4 IL_0033: ldstr "5" IL_0038: stelem.ref IL_0039: dup IL_003a: ldc.i4.5 IL_003b: ldstr "6" IL_0040: stelem.ref IL_0041: dup IL_0042: ldc.i4.6 IL_0043: ldstr "7" IL_0048: stelem.ref IL_0049: dup IL_004a: ldc.i4.7 IL_004b: ldstr "8" IL_0050: stelem.ref IL_0051: call string [System.Runtime]System.String::Format(string, object[]) IL_0056: pop IL_0057: ret } // end of method Program::FormatTest
StringFormat方法雖然是基於StringBuilder,但由於需要遍歷字元串來識別占位符,所以比較慢。
3.2Format分次拼接
private static void FormatTest2() { string s = string.Empty; s = string.Format("{0}{1}", s, "1"); s = string.Format("{0}{1}", s, "2"); s = string.Format("{0}{1}", s, "3"); s = string.Format("{0}{1}", s, "4"); s = string.Format("{0}{1}", s, "5"); s = string.Format("{0}{1}", s, "6"); s = string.Format("{0}{1}", s, "7"); s = string.Format("{0}{1}", s, "8"); }Format分次拼接常量字元串
運行時間:
記憶體情況:
IL代碼:
.method private hidebysig static void FormatTest2() cil managed { // Code size 143 (0x8f) .maxstack 3 .locals init (string V_0) IL_0000: ldsfld string [System.Runtime]System.String::Empty IL_0005: stloc.0 IL_0006: ldstr "{0}{1}" IL_000b: ldloc.0 IL_000c: ldstr "1" IL_0011: call string [System.Runtime]System.String::Format(string, object, object) IL_0016: stloc.0 IL_0017: ldstr "{0}{1}" IL_001c: ldloc.0 IL_001d: ldstr "2" IL_0022: call string [System.Runtime]System.String::Format(string, object, object) IL_0027: stloc.0 IL_0028: ldstr "{0}{1}" IL_002d: ldloc.0 IL_002e: ldstr "3" IL_0033: call string [System.Runtime]System.String::Format(string, object, object) IL_0038: stloc.0 IL_0039: ldstr "{0}{1}" IL_003e: ldloc.0 IL_003f: ldstr "4" IL_0044: call string [System.Runtime]System.String::Format(string, object, object) IL_0049: stloc.0 IL_004a: ldstr "{0}{1}" IL_004f: ldloc.0 IL_0050: ldstr "5" IL_0055: call string [System.Runtime]System.String::Format(string, object, object) IL_005a: stloc.0 IL_005b: ldstr "{0}{1}" IL_0060: ldloc.0 IL_0061: ldstr "6" IL_0066: call string [System.Runtime]System.String::Format(string, object, object) IL_006b: stloc.0 IL_006c: ldstr "{0}{1}" IL_0071: ldloc.0 IL_0072: ldstr "7" IL_0077: call string [System.Runtime]System.String::Format(string, object, object) IL_007c: stloc.0 IL_007d: ldstr "{0}{1}" IL_0082: ldloc.0 IL_0083: ldstr "8" IL_0088: call string [System.Runtime]System.String::Format(string, object, object) IL_008d: stloc.0 IL_008e: ret } // end of method Program::FormatTest2
分次使用Format方法拼接字元串,即分次調用Format方法,多次迴圈遍歷字元串,耗時更長。
4.StringBuilder方法
private static void BuilderTest() { StringBuilder sb = new StringBuilder(); sb.Append("1"); sb.Append("2"); sb.Append("3"); sb.Append("4"); sb.Append("5"); sb.Append("6"); sb.Append("7"); sb.Append("8"); }Builder拼接常量字元串
運行時間:
記憶體情況:
IL代碼:
.method private hidebysig static void BuilderTest() cil managed { // Code size 101 (0x65) .maxstack 3 IL_0000: newobj instance void [System.Runtime]System.Text.StringBuilder::.ctor() IL_0005: dup IL_0006: ldstr "1" IL_000b: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string) IL_0010: pop IL_0011: dup IL_0012: ldstr "2" IL_0017: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string) IL_001c: pop IL_001d: dup IL_001e: ldstr "3" IL_0023: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string) IL_0028: pop IL_0029: dup IL_002a: ldstr "4" IL_002f: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string) IL_0034: pop IL_0035: dup IL_0036: ldstr "5" IL_003b: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string) IL_0040: pop IL_0041: dup IL_0042: ldstr "6" IL_0047: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string) IL_004c: pop IL_004d: dup IL_004e: ldstr "7" IL_0053: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string) IL_0058: pop IL_0059: ldstr "8" IL_005e: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string) IL_0063: pop IL_0064: ret } // end of method Program::BuilderTest
在短字元串大量拼接的測試中,可以看出一次使用+進行拼接所耗記憶體最少,所用時間最短。其餘方式所耗記憶體相差不大,但StringBuilder所耗時間明顯較短。
由於Format方法內部存在對字元串的遍歷,可以推測,隨著字元串的長度變長,Format方法所耗時間將會增加。
二、字元串變數拼接(多次迴圈拼接)
private static void TestPerformanceWithParam<T>(Action<T> action, T t, int times) { Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < times; i++) { action(t); } sw.Stop(); Console.WriteLine(action.Method.Name + ", FullTime: " + sw.ElapsedMilliseconds); }變數字元串測試方法
1.+方法
1.1連續拼接
private static void AddTest(string t) { string s = string.Empty; s = t + t + t + t + t + t + t + t; }Plus字元串變數連續拼接
運行時間:
記憶體情況:
IL代碼:
.method private hidebysig static void AddTest(string t) cil managed { // Code size 51 (0x33) .maxstack 8 IL_0000: ldsfld string [System.Runtime]System.String::Empty IL_0005: pop IL_0006: ldc.i4.8 IL_0007: newarr [System.Runtime]System.String IL_000c: dup IL_000d: ldc.i4.0 IL_000e: ldarg.0 IL_000f: stelem.ref IL_0010: dup IL_0011: ldc.i4.1 IL_0012: ldarg.0 IL_0013: stelem.ref IL_0014: dup IL_0015: ldc.i4.2 IL_0016: ldarg.0 IL_0017: stelem.ref IL_0018: dup IL_0019: ldc.i4.3 IL_001a: ldarg.0 IL_001b: stelem.ref IL_001c: dup IL_001d: ldc.i4.4 IL_001e: ldarg.0 IL_001f: stelem.ref IL_0020: dup IL_0021: ldc.i4.5 IL_0022: ldarg.0 IL_0023: stelem.ref IL_0024: dup IL_0025: ldc.i4.6 IL_0026: ldarg.0 IL_0027: stelem.ref IL_0028: dup IL_0029: ldc.i4.7 IL_002a: ldarg.0 IL_002b: stelem.ref IL_002c: call string [System.Runtime]System.String::Concat(string[]) IL_0031: pop IL_0032: ret } // end of method Program::AddTestPlus字元串變數拼接IL
從IL代碼可見,在使用+連續拼接字元串變數時,內部調用了String.Concat(string[])方法。
1.2分段拼接
private static void AddWithParamTest2(string t) { string s = string.Empty; s += t; s += t; s += t; s += t; s += t; s += t; s += t; s += t; }Plus字元串變數拼接分段
運行時間:
記憶體情況:
IL代碼:
.method private hidebysig static void AddWithParamTest2(string t) cil managed { // Code size 55 (0x37) .maxstack 8 IL_0000: ldsfld string [System.Runtime]System.String::Empty IL_0005: ldarg.0 IL_00