In normal development, most of you will follow the interface programming, 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. See your application scenarios for choices.
1: Background
1. Reason
In the performance transformation of the project, it is found that the return values of many method signatures are all based on 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 doesn't have any performance problems. foreach iteration is natural. How can this be optimized???
<1> Looking for 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
From the IL, we can see that the standard get īšŖ current, MoveNext, dispose also has a try,finally. There are so many methods and keywords all at once. Isn't it just a simple foreach iteration array? As for the complexity? How can it get up quickly under big data?
There is another wonderful thing. If you carefully observe the IL code, such as this sentence: [mscorlib] system. Collections. Generic. IEnumerable ` ` 1 < int32 >:: getenumerator(), the GetEnumerator is preceded by the interface IEnumerable. Normally, it should be a specific iteration class. It should 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> Looking for problems from windbg
The second question I found in IL is very curious, đđ , let's go to the managed heap to see which specific class called the GetEnumerator() method.
! clrstack - L >! Do XX grab list variable on 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, but it's the ghost of JIT. It generates such a SZGenericArrayEnumerator type. Next, type its method table to see what's 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)
As you can see, this is a standard iteration class, and this performance has been dragged down again...
2: Optimize performance
Based on the above analysis, it seems that the problem lies in foreach and IEnumerable < int >.
1. IEnumerable < int > replace int [], foreach change to for
Now that you know these two points, modify the code 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
It can be seen that the above IL instructions are very basic instructions. Most of them are directly supported by CPU instructions. They are very simple and love each other~~~
Here's one thing to note: I later observed that foreach didn't need to be changed to for, and the vs editor helped us transform it at the bottom. It can be seen that foreach is still very intelligent when iterating array types, and I know 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
In the micro aspect, I've taken you through the analysis. Next, I'll test the performance difference between the two methods. I'll make 10 performance comparisons for each method.
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
It's unbelievable that there's a gap of 3-4 times... That's the price of flexibility for performance đđđ
Well, that's all. I hope it can help you.