自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面向接口編程,你考慮過性能嗎?

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

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

一:背景

1. 緣由

在項目的性能改造中,發(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. 有什么問題

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

<1> 從MSIL中尋找問題

首先我們盡可能把原貌還原出來,簡化后的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.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

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

還有一個奇葩的事,如果你仔細(xì)觀察IL代碼,比如這句:[mscorlib]System.Collections.Generic.IEnumerable``1<int32>::GetEnumerator(), 這個GetEnumerator前面是接口IEnumerable,正常情況下應(yīng)該是具體迭代類吧,按理說應(yīng)該會調(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中尋找問題

IL中發(fā)現(xiàn)的第二個問題我特別好奇,????,我們到托管堆上去看下到底是哪一個具體類調(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 = 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  <<

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

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)

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

二:優(yōu)化性能

綜合上面分析,貌似問題出在了 foreach 和 IEnumerable<int>這兩個方面。

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

知道了這兩點,接下來把代碼修改如下:

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

可以看到上面的IL指令都是非?;A(chǔ)的指令,大多都有CPU指令直接提供支持,非常簡潔,大愛~~~

這里有一點要注意:我后來觀察foreach不需要改成for,vs編輯器在底層幫我們轉(zhuǎn)換了,看的出來foreach在迭代數(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.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. 代碼測試

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

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},時間:{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},時間:{watch.ElapsedMilliseconds}");
            }
            Console.ReadLine();
        }

        public static int[] GetHasEmailCustomerIDArray()
        {
            return Enumerable.Range(1, 5000000).ToArray();
        }

i=0,時間:10
i=1,時間:10
i=2,時間:10
i=3,時間:9
i=4,時間:9
i=5,時間:9
i=6,時間:10
i=7,時間:10
i=8,時間:12
i=9,時間:12
---------------
i=0,時間:45
i=1,時間:37
i=2,時間:35
i=3,時間:35
i=4,時間:37
i=5,時間:35
i=6,時間:36
i=7,時間:37
i=8,時間:35
i=9,時間:36

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

責(zé)任編輯:武曉燕 來源: 一線碼農(nóng)聊技術(shù)
相關(guān)推薦

2021-07-12 07:59:05

對象接口編程

2013-07-30 09:42:41

實現(xiàn)編程接口編程對象編程

2015-10-26 09:52:26

bat裁員信息安全

2024-04-24 13:59:02

云原生應(yīng)用

2016-04-19 16:01:05

2020-07-23 17:29:47

接口編程代碼

2021-01-14 08:16:41

Python接口編程

2024-08-05 01:24:54

2020-06-20 14:09:01

信息安全數(shù)據(jù)技術(shù)

2009-07-02 13:25:00

消除實現(xiàn)繼承面向接口編程Java

2023-07-06 08:31:50

Python對象編程

2021-05-28 05:34:06

Golang語言編程

2020-03-11 20:42:34

瀏覽器緩存機制

2021-09-25 13:12:47

數(shù)據(jù)開發(fā)架構(gòu)

2023-10-13 07:36:58

Java函數(shù)式編程

2012-02-01 10:18:23

編程

2020-07-14 07:48:19

Java對象JVM

2023-03-13 13:36:00

Go擴容切片

2022-01-05 12:03:48

MySQL索引數(shù)據(jù)

2023-05-29 08:11:42

@Value注解Bean
點贊
收藏

51CTO技術(shù)棧公眾號