日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

面向接口编程,你考虑过性能吗?

發(fā)布時(shí)間:2023/12/4 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面向接口编程,你考虑过性能吗? 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

大家在平時(shí)開發(fā)中大多都會(huì)遵循接口編程,這樣就可以方便實(shí)現(xiàn)依賴注入也方便實(shí)現(xiàn)多態(tài)等各種小技巧,但這種是以犧牲性能為代價(jià)換取代碼的靈活性,萬(wàn)物皆有陰陽(yáng),看你的應(yīng)用場(chǎng)景進(jìn)行取舍。

一:背景

1. 緣由

在項(xiàng)目的性能改造中,發(fā)現(xiàn)很多方法簽名的返回值都是采用IEnumerable接口,比如下面這段代碼:

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. 有什么問(wèn)題

這段代碼乍一看也沒(méi)啥什么性能問(wèn)題,foreach迭代天經(jīng)地義,這個(gè)還能怎么優(yōu)化???

<1> 從MSIL中尋找問(wèn)題

首先我們盡可能把原貌還原出來(lái),簡(jiǎn)化后的MSIL如下。

.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.1IL_0012: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()IL_0017: stloc.2IL_0018: nopIL_0019: nopIL_001a: ldloc.1IL_001b: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()IL_0020: brtrue.s IL_0011// end loopIL_0022: leave.s IL_002f} // end .tryfinally{IL_0024: ldloc.1IL_0025: brfalse.s IL_002eIL_0027: ldloc.1IL_0028: callvirt instance void [mscorlib]System.IDisposable::Dispose()IL_002d: nopIL_002e: endfinally} // end handlerIL_002f: ret } // end of method Program::Main

從IL中看到了標(biāo)準(zhǔn)的get_Current,MoveNext,Dispose?還有一個(gè)try,finally,一下子多了這么多方法和關(guān)鍵詞,不就是一個(gè)簡(jiǎn)單的foreach迭代數(shù)組嘛?至于搞的這么復(fù)雜嘛?這樣在大數(shù)據(jù)下怎么快的起來(lái)?

還有一個(gè)奇葩的事,如果你仔細(xì)觀察IL代碼,比如這句:[mscorlib]System.Collections.Generic.IEnumerable``1<int32>::GetEnumerator(), 這個(gè)GetEnumerator前面是接口IEnumerable,正常情況下應(yīng)該是具體迭代類吧,按理說(shuō)應(yīng)該會(huì)調(diào)用Array的GetEnumerator方法,如下所示。

[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> 從windbg中尋找問(wèn)題

IL中發(fā)現(xiàn)的第二個(gè)問(wèn)題我特別好奇,????????,我們到托管堆上去看下到底是哪一個(gè)具體類調(diào)用了GetEnumerator()方法。

!clrstack -l > !do xx 到線程棧上抓list變量

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 = 0x0000019bf33b9a880x000000229e3fede0 = 0x0000019be33b2d900x000000229e3fedfc = 0x00000000004c4b400: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 <<

居然有這么一個(gè)類型?Name: System.SZArrayHelper+SZGenericArrayEnumerator,然來(lái)是JIT搗的鬼,生成了這么一個(gè)SZGenericArrayEnumerator類型,接下來(lái)把它的方法表打出來(lái)看看里面都有啥方法。

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 TableEntry 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)

可以看到這是一個(gè)標(biāo)準(zhǔn)的迭代類,這性能又被拖累了。。。

二:優(yōu)化性能

綜合上面分析,貌似問(wèn)題出在了?foreach?和?IEnumerable<int>這兩個(gè)方面。

1. IEnumerable?替換 int[], foreach改成for

知道了這兩點(diǎn),接下來(lái)把代碼修改如下:

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.0IL_0008: stloc.1// (no C# code)IL_0009: br.s IL_0011// loop start (head: IL_0011)IL_000b: nopIL_000c: nop// for (int i = 0; i < hasEmailCustomerIDList.Length; i++)IL_000d: ldloc.1IL_000e: ldc.i4.1IL_000f: addIL_0010: stloc.1// for (int i = 0; i < hasEmailCustomerIDList.Length; i++)IL_0011: ldloc.1IL_0012: ldloc.0IL_0013: ldlenIL_0014: conv.i4IL_0015: cltIL_0017: stloc.2IL_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

可以看到上面的IL指令都是非常基礎(chǔ)的指令,大多都有CPU指令直接提供支持,非常簡(jiǎn)潔,大愛~~~

這里有一點(diǎn)要注意:我后來(lái)觀察foreach不需要改成for,vs編輯器在底層幫我們轉(zhuǎn)換了,看的出來(lái)foreach在迭代數(shù)組類型的時(shí)候還是非常智能的,知道怎么幫助我們優(yōu)化。。。修改代碼如下:

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.0IL_0009: stloc.1// for (int i = 0; i < array.Length; i++)IL_000a: ldc.i4.0IL_000b: stloc.2// (no C# code)IL_000c: br.s IL_0018// loop start (head: IL_0018)// int num = array[i];IL_000e: ldloc.1IL_000f: ldloc.2IL_0010: ldelem.i4// (no C# code)IL_0011: stloc.3IL_0012: nopIL_0013: nop// for (int i = 0; i < array.Length; i++)IL_0014: ldloc.2IL_0015: ldc.i4.1IL_0016: addIL_0017: stloc.2// for (int i = 0; i < array.Length; i++)IL_0018: ldloc.2IL_0019: ldloc.1IL_001a: ldlenIL_001b: conv.i4IL_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. 代碼測(cè)試

微觀方面已經(jīng)帶大家分析過(guò)了,接下來(lái)宏觀測(cè)試兩種方式的性能到底相差多少,每一個(gè)方法我都做10次性能對(duì)比。

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},時(shí)間:{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},時(shí)間:{watch.ElapsedMilliseconds}");}Console.ReadLine();}public static int[] GetHasEmailCustomerIDArray(){return Enumerable.Range(1, 5000000).ToArray();}i=0,時(shí)間:10 i=1,時(shí)間:10 i=2,時(shí)間:10 i=3,時(shí)間:9 i=4,時(shí)間:9 i=5,時(shí)間:9 i=6,時(shí)間:10 i=7,時(shí)間:10 i=8,時(shí)間:12 i=9,時(shí)間:12 --------------- i=0,時(shí)間:45 i=1,時(shí)間:37 i=2,時(shí)間:35 i=3,時(shí)間:35 i=4,時(shí)間:37 i=5,時(shí)間:35 i=6,時(shí)間:36 i=7,時(shí)間:37 i=8,時(shí)間:35 i=9,時(shí)間:36

難以置信的是居然有3-4倍的差距。。。這就是用靈活性換取性能的代價(jià)????????????

好了,本篇就說(shuō)到這里,希望對(duì)你有幫助。

總結(jié)

以上是生活随笔為你收集整理的面向接口编程,你考虑过性能吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。