Most of us will follow interface programming in normal development, which can facilitate the implementation of dependency injection, polymorphism and other small skills, but this is at the expense of performance in exchange for code flexibility. Everything has Yin and Yang. It depends on your application scenario.
1: Background
1. Reason
During the performance transformation of the project, it is found that the return values of many method signatures adopt the IEnumerable interface, such as the following code:
public static void Main(string[] args) { var list = GetHasEmailCustomerIDList(); foreach (var item in list){} Console.ReadLine(); } public static IEnumerable<int> GetHasEmailCustomerIDList() { return Enumerable.Range(1, 5000000).ToArray(); }
2. What's the problem
At first glance, this code does not have any performance problems. foreach iteration is natural. How can this be optimized???
<1> Find problems from MSIL
First, we try to restore the original appearance as much as possible. The simplified MSIL is as follows.
.method public hidebysig static void Main ( string[] args ) cil managed { IL_0009: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator() IL_000e: stloc.1 .try { IL_000f: br.s IL_001a // loop start (head: IL_001a) IL_0011: ldloc.1 IL_0012: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current() IL_0017: stloc.2 IL_0018: nop IL_0019: nop IL_001a: ldloc.1 IL_001b: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() IL_0020: brtrue.s IL_0011 // end loop IL_0022: leave.s IL_002f } // end .try finally { IL_0024: ldloc.1 IL_0025: brfalse.s IL_002e IL_0027: ldloc.1 IL_0028: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_002d: nop IL_002e: endfinally } // end handler IL_002f: ret } // end of method Program::Main
See the standard get from IL_ Current, MoveNext, dispose , and try,finally. With so many methods and keywords, isn't it a simple foreach iterative array? As for making it so complicated? How can it get up quickly under big data?
Another wonderful thing is that if you carefully observe the IL code, such as this sentence: [mscorlib] system Collections. Generic. IEnumerable ` ` 1 < int32 >:: getenumerator(), which is preceded by the interface IEnumerable. Under normal circumstances, it should be a specific iterative class. It is reasonable to call the GetEnumerator method of Array, as shown below.
[Serializable] [ComVisible(true)] [__DynamicallyInvokable] public abstract class Array : ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable { [__DynamicallyInvokable] public IEnumerator GetEnumerator() { int lowerBound = GetLowerBound(0); if (Rank == 1 && lowerBound == 0) { return new SZArrayEnumerator(this); } return new ArrayEnumerator(this, lowerBound, Length); } }
<2> Finding problems from windbg
The second question I found in IL is particularly curious, 😄😄, Let's go to the managed heap to see which specific class called the GetEnumerator() method.
! clrstack -l > ! Do XX grab the list variable on the thread stack
0:000> !clrstack -l 000000229e3feda0 00007ff889e40951 *** WARNING: Unable to verify checksum for ConsoleApp2.exe ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 32] LOCALS: 0x000000229e3fede8 = 0x0000019bf33b9a88 0x000000229e3fede0 = 0x0000019be33b2d90 0x000000229e3fedfc = 0x00000000004c4b40 0:000> !do 0x0000019be33b2d90 Name: System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]] MethodTable: 00007ff8e8d36d18 EEClass: 00007ff8e7cf5640 Size: 32(0x20) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name 00007ff8e7a98538 4002ffe 8 System.Int32[] 0 instance 0000019bf33b9a88 _array 00007ff8e7a985a0 4002fff 10 System.Int32 1 instance 5000000 _index 00007ff8e7a985a0 4003000 14 System.Int32 1 instance 5000000 _endIndex 00007ff8e8d36d18 4003001 0 ...Int32, mscorlib]] 0 shared static Empty >> Domain:Value dynamic statics NYI 0000019be1893a80:NotInit <<
There is such a type name: system Szarrayhelper + SZGenericArrayEnumerator, however, is the ghost of JIT. It generates such a SZGenericArrayEnumerator type. Next, type its method table to see what methods are in it.
0:000> !dumpmt -md 00007ff8e8d36d18 EEClass: 00007ff8e7cf5640 Module: 00007ff8e7a71000 Name: System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]] mdToken: 0000000002000a98 File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll BaseSize: 0x20 ComponentSize: 0x0 Slots in VTable: 11 Number of IFaces in IFaceMap: 3 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 00007ff8e7ff2450 00007ff8e7a78de8 PreJIT System.Object.ToString() 00007ff8e800cc60 00007ff8e7c3b9b0 PreJIT System.Object.Equals(System.Object) 00007ff8e7ff2090 00007ff8e7c3b9d8 PreJIT System.Object.GetHashCode() 00007ff8e7fef420 00007ff8e7c3b9e0 PreJIT System.Object.Finalize() 00007ff8e8b99fd0 00007ff8e7ebf388 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].MoveNext() 00007ff8e8b99f90 00007ff8e7ebf390 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].get_Current() 00007ff8e8b99f60 00007ff8e7ebf398 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].System.Collections.IEnumerator.get_Current() 00007ff8e8b99f50 00007ff8e7ebf3a0 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].System.Collections.IEnumerator.Reset() 00007ff8e8b99f40 00007ff8e7ebf3a8 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].Dispose() 00007ff8e8b99ef0 00007ff8e7ebf3b0 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]]..cctor() 00007ff8e8b99ff0 00007ff8e7ebf380 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]]..ctor(Int32[], Int32)
You can see that this is a standard iterative class, which is a drag on performance...
2: Optimize performance
Based on the above analysis, it seems that the problem lies in two aspects: foreach and IEnumerable < int >.
1. IEnumerable , replace int [], foreach with for
After knowing these two points, the code is modified as follows:
public static void Main(string[] args) { var list = GetHasEmailCustomerIDList(); for (int i = 0; i < list.Length; i++) { } Console.ReadLine(); } public static int[] GetHasEmailCustomerIDList() { return Enumerable.Range(1, 5000000).ToArray(); } .method public hidebysig static void Main ( string[] args ) cil managed { // (no C# code) IL_0000: nop // int[] hasEmailCustomerIDList = GetHasEmailCustomerIDList(); IL_0001: call int32[] ConsoleApp2.Program::GetHasEmailCustomerIDList() IL_0006: stloc.0 // for (int i = 0; i < hasEmailCustomerIDList.Length; i++) IL_0007: ldc.i4.0 IL_0008: stloc.1 // (no C# code) IL_0009: br.s IL_0011 // loop start (head: IL_0011) IL_000b: nop IL_000c: nop // for (int i = 0; i < hasEmailCustomerIDList.Length; i++) IL_000d: ldloc.1 IL_000e: ldc.i4.1 IL_000f: add IL_0010: stloc.1 // for (int i = 0; i < hasEmailCustomerIDList.Length; i++) IL_0011: ldloc.1 IL_0012: ldloc.0 IL_0013: ldlen IL_0014: conv.i4 IL_0015: clt IL_0017: stloc.2 IL_0018: ldloc.2 // (no C# code) IL_0019: brtrue.s IL_000b // end loop // Console.ReadLine(); IL_001b: call string [mscorlib]System.Console::ReadLine() // (no C# code) IL_0020: pop // } IL_0021: ret } // end of method Program::Main
You can see that the above IL instructions are very basic instructions. Most of them are directly supported by CPU instructions. They are very concise and love~~~
Here's a point to note: I later observed that foreach does not need to be changed to for. The vs editor helps us convert at the bottom. I can see that foreach is still very intelligent when iterating array types and knows how to help us optimize... The modification code is as follows:
public static void Main(string[] args) { var list = GetHasEmailCustomerIDList(); //for (int i = 0; i < list.Length; i++) { } foreach (var item in list) { } Console.ReadLine(); } .method public hidebysig static void Main ( string[] args ) cil managed { // (no C# code) IL_0000: nop // int[] hasEmailCustomerIDList = GetHasEmailCustomerIDList(); IL_0001: call int32[] ConsoleApp2.Program::GetHasEmailCustomerIDList() IL_0006: stloc.0 // (no C# code) IL_0007: nop // int[] array = hasEmailCustomerIDList; IL_0008: ldloc.0 IL_0009: stloc.1 // for (int i = 0; i < array.Length; i++) IL_000a: ldc.i4.0 IL_000b: stloc.2 // (no C# code) IL_000c: br.s IL_0018 // loop start (head: IL_0018) // int num = array[i]; IL_000e: ldloc.1 IL_000f: ldloc.2 IL_0010: ldelem.i4 // (no C# code) IL_0011: stloc.3 IL_0012: nop IL_0013: nop // for (int i = 0; i < array.Length; i++) IL_0014: ldloc.2 IL_0015: ldc.i4.1 IL_0016: add IL_0017: stloc.2 // for (int i = 0; i < array.Length; i++) IL_0018: ldloc.2 IL_0019: ldloc.1 IL_001a: ldlen IL_001b: conv.i4 IL_001c: blt.s IL_000e // end loop // Console.ReadLine(); IL_001e: call string [mscorlib]System.Console::ReadLine() // (no C# code) IL_0023: pop // } IL_0024: ret } // end of method Program::Main
2. Code test
The micro aspect has been analyzed with you. Next, I will test the performance difference between the two methods. I will compare the performance of each method for 10 times.
public static void Main(string[] args) { var arr = GetHasEmailCustomerIDArray(); for (int i = 0; i < 10; i++) { var watch = Stopwatch.StartNew(); foreach (var item in arr) { } watch.Stop(); Console.WriteLine($"i={i},time:{watch.ElapsedMilliseconds}"); } Console.WriteLine("---------------"); var list = arr as IEnumerable<int>; for (int i = 0; i < 10; i++) { var watch = Stopwatch.StartNew(); foreach (var item in list) { } watch.Stop(); Console.WriteLine($"i={i},time:{watch.ElapsedMilliseconds}"); } Console.ReadLine(); } public static int[] GetHasEmailCustomerIDArray() { return Enumerable.Range(1, 5000000).ToArray(); } i=0,time:10 i=1,time:10 i=2,time:10 i=3,time:9 i=4,time:9 i=5,time:9 i=6,time:10 i=7,time:10 i=8,time:12 i=9,time:12 --------------- i=0,time:45 i=1,time:37 i=2,time:35 i=3,time:35 i=4,time:37 i=5,time:35 i=6,time:36 i=7,time:37 i=8,time:35 i=9,time:36