<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      代碼改變世界

      從匯編入手,探究泛型的性能問題

      2009-05-30 05:21  Jeffrey Zhao  閱讀(25843)  評論(81)    收藏  舉報

      經過了《泛型真的會降低性能嗎?》一文中的性能測試,已經從實際入手,從測試數據上證明了泛型不會降低程序效率。只是還是有幾位朋友談到,“普遍認為”泛型的代碼性能會略差一些,也有朋友正在進一步尋找泛型性能略差的證據。老趙認為這種探究問題的方式非常值得提倡。不過,老趙忽然想到,如果從能從匯編入手,證明非泛型和泛型的代碼之間沒有性能差距——好吧,或者說,存在性能差距,那么事情不就到此為止了嗎?任何理論說明,都抵不過觀察計算機是如何處理這個問題來的“直接”。因此,老趙最終決定通過這種極端的方式來一探究竟,把這個問題徹底解決。

      需要一提的是,老趙并不希望這篇文章會引起一些不必要的爭論,因此一些話就先說在前面。老趙并不喜歡用這種方式來解決問題。事實上,如果可以通過數據比較,理論分析,或者高級代碼來說明問題,我連IL都不愿意接觸,更別說深入匯編。如果是平時的工作,就算使用WinDbg也最多是查看查看內存中有哪些數據,系統到底出了哪些問題。如果您要老趙表態的話,我會說:我強烈反對接觸匯編。我們有太多太多的東西需要學習,如果您并沒有明確您的目標,老趙建議您就放過IL和匯編這種東西吧。我們知道這些是什么就行了,不必對它們有什么“深入”的了解。

      下面就要開始真正的探索之旅了。這不是一個順利的旅程,其中有些步驟是連蒙帶猜,最后加以驗證才得到的結果。原本老趙打算按照自己的思路一步一步進行下去,但是發現這樣太過冗余,反而會讓大家的思路難以集中。因此老趙最后決定重新設計一個流程,和大家一起步步為營,朝著目標前進。此外,為了方便某些朋友按照這文章親手進行操作,老趙也制作了一個dump文件,如果您是安裝了.NET 3.5 SP1的32位x86系統,可以直接下載進行試驗。試驗過程中出現的地址也會和文章中完全一致。

      廢話就說到這里,我們開始吧。

      測試代碼

      測試代碼便是我們的目標。和上一篇文章一樣,我們準備了一份最簡單的代碼進行測試,這樣可以盡可能擺脫其他因素的影響,得到最正確的結果:

      namespace TestConsole
      {
          public class MyArrayList
          {
              public MyArrayList(int length)
              {
                  this.m_items = new object[length];
              }
      
              private object[] m_items;
      
              public object this[int index]
              {
                  [MethodImpl(MethodImplOptions.NoInlining)]
                  get
                  {
                      return this.m_items[index];
                  }
                  [MethodImpl(MethodImplOptions.NoInlining)]
                  set
                  {
                      this.m_items[index] = value;
                  }
              }
          }
      
          public class MyList<T>
          {
              public MyList(int length)
              {
                  this.m_items = new T[length];
              }
      
              private T[] m_items;
      
              public T this[int index]
              {
                  [MethodImpl(MethodImplOptions.NoInlining)]
                  get
                  {
                      return this.m_items[index];
                  }
                  [MethodImpl(MethodImplOptions.NoInlining)]
                  set
                  {
                      this.m_items[index] = value;
                  }
              }
          }
      
          class Program
          {
              static void Main(string[] args)
              {
                  MyArrayList arrayList = new MyArrayList(1);
                  arrayList[0] = arrayList[0] ?? new object();
      
                  MyList<object> list = new MyList<object>(1);
                  list[0] = list[0] ?? new object();
      
                  Console.WriteLine("Here comes the testing code.");
      
                  var a = arrayList[0];
                  var b = list[0];
      
                  Console.ReadLine();
              }
          }
      }
      

      我們在這里構建了兩個“容器”,一個是MyArrayList,另一個是MyList<T>,前者直接使用Object類型,而后者則是一個泛型類。我們對兩個類的索引屬性的get和set方法都加上了NoInlining標記,這樣便可以避免這種簡單的方法被JIT內聯。而在Main方法中,前幾行代碼的作用都是構造兩個類的對象,并確保索引的get和set方法都已經得到JIT。在打印出“Here comes the testing code.”之后,我們便對兩個類的實例進行“下標訪問”,并使控制臺暫停。

      當Release編譯并運行之后,控制臺會打印出“Here comes the testing code.”字樣并停止。這時候我們便可以使用WinDbg來Attach to Process進行調試。老趙也是在這個時候制作了一個dump文件,您也可以Open Crash Dump命令打開這個文件。更多操作您可以參考互聯網上的各篇文章,亦或是老趙之前寫過的一篇《使用WinDbg獲得托管方法的匯編代碼》。

      分析MyArrayList對象結構

      假設您現在已經打開了WinDbg,并Attach to Process(或Open Crash Dump),而且加載了正確的sos.dll(可參考老趙之前給出的文章)。那么第一件事情,我們就要來分析一個MyArrayList對象的結構。

      首先,我們還是在項目中查找MyArrayList類型的MT(Method Table,方法表)地址:

      0:000> !name2ee *!TestConsole.MyArrayList
      Module: 5bf71000 (mscorlib.dll)
      --------------------------------------
      Module: 00362354 (sortkey.nlp)
      --------------------------------------
      Module: 00362010 (sorttbls.nlp)
      --------------------------------------
      Module: 00362698 (prcp.nlp)
      --------------------------------------
      Module: 003629dc (mscorlib.resources.dll)
      --------------------------------------
      Module: 00342ff8 (TestConsole.exe)
      Token: 0x02000002
      MethodTable: 00343440
      EEClass: 0034141c
      Name: TestConsole.MyArrayList
      

      我們得到了MyArrayList類型的MT地址之后,便可以在系統中尋找MyArrayList對象了:

      0:000> !dumpheap -mt 00343440
       Address       MT     Size
      0205be3c 00343440       12
      total 1 objects
      Statistics:
            MT    Count    TotalSize Class Name
      00343440        1           12 TestConsole.MyArrayList
      Total 1 objects
      

      不出所料,當前程序中只有一個MyArrayList對象。我們繼續追蹤它的地址:

      0:000> !do 0205be3c
      Name: TestConsole.MyArrayList
      MethodTable: 00343440
      EEClass: 0034141c
      Size: 12(0xc) bytes
       (E:\Users\Jeffrey Zhao\...\bin\Release\TestConsole.exe)
      Fields:
            MT    Field   Offset                 Type VT     Attr    Value Name
      5c1b41d0  4000001        4      System.Object[]  0 instance 0205be48 m_items
      

      OK,到這里為止,我們得到一個結論。如果我們獲得了一個MyArrayList對象的地址,那么偏移4個字節,便可以得到m_items字段,也就是存放元素的Object數組的地址。這點很關鍵,否則可能對于理解后面的匯編代碼形成障礙。

      如果您使用同樣的方法來觀察MyList<object>類型的話,您會發現其結果也完全相同:從對象地址開始偏移4個字節便是m_items字段,類型為Object數組。

      分析數組對象的結構

      接著我們來觀察一下,一個數組對象在內存中的存放方式是什么樣的。首先,我們打印出托管堆上的各種類型:

      0:000> !dumpheap -stat
      total 6922 objects
      Statistics:
            MT    Count    TotalSize Class Name
      5c1e3ed4        1           12 System.Text.DecoderExceptionFallback
      5c1e3e90        1           12 System.Text.EncoderExceptionFallback
      5c1e1ea4        1           12 System.RuntimeTypeHandle
      5c1dfb28        1           12 System.__Filters
      5c1dfad8        1           12 System.Reflection.Missing
      5c1df9e0        1           12 System.RuntimeType+TypeCacheQueue
      ...
      5c1e3150       48         8640 System.Collections.Hashtable+bucket[]
      5c1e2d28      347         9716 System.Collections.ArrayList+ArrayListEnumeratorSimple
      5c1b5ca4       46        11024 System.Reflection.CustomAttributeNamedParameter[]
      5c1cc590      404        11312 System.Security.SecurityElement
      5c1e2a30      578        13872 System.Collections.ArrayList
      5c1b50e4      335        14740 System.Int16[]
      5c1b41d0     1735        87172 System.Object[]
      5c1e0a00      718       167212 System.String
      5c1e3470       70       174272 System.Byte[]
      Total 6922 objects

      既然我們的代碼中使用了Object數組,那么我們就把目標放在托管堆上的Object數組中。從上面的信息中我們已經獲得了Object數組的MT地址,于是我們繼續列舉出托管堆上的此類對象:

      0:000> !dumpheap -mt 5c1b41d0
       Address       MT     Size
      01fd141c 5c1b41d0       80     
      01fd1c84 5c1b41d0       16     
      01fd1cc0 5c1b41d0       32     
      ...
      0205baa4 5c1b41d0       20     
      0205bc4c 5c1b41d0       20     
      0205bc60 5c1b41d0       32     
      0205bdc4 5c1b41d0       16     
      0205be48 5c1b41d0       20     
      0205be74 5c1b41d0       20     
      0205c058 5c1b41d0       36     
      02fd1010 5c1b41d0     4096     
      02fd2020 5c1b41d0      528     
      02fd2240 5c1b41d0     4096     
      total 1735 objects
      Statistics:
            MT    Count    TotalSize Class Name
      5c1b41d0     1735        87172 System.Object[]
      Total 1735 objects
      

      我們隨意抽取一個Object數組對象,查看它的內容:

      0:000> !do 02fd2020
      Name: System.Object[]
      MethodTable: 5c1b41d0
      EEClass: 5bf9da54
      Size: 528(0x210) bytes
      Array: Rank 1, Number of elements 128, Type CLASS
      Element Type: System.Object
      Fields:
      None
      

      WinDbg清楚明白地告訴我們,這個數組是1維的,共有128個元素。那么這個數組的長度信息是如何保存下來的呢(這個信息肯定是對象自帶的,這個很容易理解吧)?我們直接查看這個數組對象地址上的數據吧:

      0:000> dd 02fd2020
      02fd2020  5c1b41d0 00000080 5c1e061c 01fd1198
      02fd2030  0205bdf0 00000000 00000000 00000000
      02fd2040  00000000 00000000 00000000 00000000
      02fd2050  00000000 00000000 00000000 00000000
      02fd2060  00000000 00000000 00000000 00000000
      02fd2070  00000000 00000000 00000000 00000000
      02fd2080  00000000 00000000 00000000 00000000
      02fd2090  00000000 00000000 00000000 00000000
      

      十六進制數00000080不就是十進制的128嗎?沒錯,老趙對多個數組對象進行分析之后,發現數組對象存放的結構是從對象的地址開始:

      • 偏移0字節:存放了這個數組對象的MT地址,例如上面的5c1b41d0便是Object[]類型的MT地址。
      • 偏移4字節:存放了數組長度。
      • 偏移8字節:存放了數組元素類型的MT地址,例如上面的5c1e061c便是Object類型的MT地址,您可以使用!dumpmt -md 5c1e061c指令進行觀察。
      • 偏移12字節:從這里開始,便存放了數組的每個元素了。也就是說,如果這是一個引用類型的數組,那么偏移12字節則存放了第1個(下標為0)元素的地址,偏移16字節則存放第2個元素的地址,以此類推。

      實際上,這些是老趙在自己的試驗過程中,從接下去會講解的匯編代碼出發猜測出來的結果,經過驗證發現恰好符合。為了避免您走這些彎路,老趙就先將這一結果告訴大家了。

      分析Main函數的匯編代碼

      接下去便要觀察Main函數的匯編代碼了。獲取匯編代碼的方法很簡單,如果您對此還不太了解,老趙的文章《使用WinDbg獲得托管方法的匯編代碼》會給您一定幫助。Main函數的匯編代碼如下:

      0:000> !u 01d40070
      Normal JIT generated code
      TestConsole.Program.Main(System.String[])
      Begin 01d40070, size e2
      >>> 01d40070  push    ebp
      01d40071  mov     ebp,esp
      01d40073  push    edi
      01d40074  push    esi
      01d40075  push    ebx
      ...
      01d4011d  mov     ecx,eax
      // 打印字樣“Here comes the testing code.”
      01d4011f  mov     edx,dword ptr ds:[2FD2030h] ("Here comes the testing code.")
      01d40125  mov     eax,dword ptr [ecx]
      01d40127  call    dword ptr [eax+0D8h]
      // 將MyArrayList對象的地址保存在ecx寄存器中
      01d4012d  mov     ecx,esi
      // 將edx寄存器清零,作為訪問下面get_Item方法的參數
      01d4012f  xor     edx,edx
      // 獲取地址0x343424中的數據(它是get_Item方法的訪問入口),并調用
      01d40131  call    dword ptr ds:[343424h] (...MyArrayList.get_Item(Int32), ...)
      // 將MyList<object>對象的地址保存在ecx寄存器中
      01d40137  mov     ecx,edi
      // 將edx寄存器清零,作為訪問下面get_Item方法的參數
      01d40139  xor     edx,edx
      // 獲取地址0x343594中的數據(它是get_Item方法的訪問入口),并調用
      01d4013b  call    dword ptr ds:[343594h] (...MyList`1[...].get_Item(Int32), ...)
      // 調用Console.ReadLine方法,請注意靜態方法不需要把對象地址放到ecx寄存器中
      01d40141  call    mscorlib_ni+0x6d1af4 (5c641af4) (System.Console.get_In(), ...)
      01d40146  mov     ecx,eax
      01d40148  mov     eax,dword ptr [ecx]
      01d4014a  call    dword ptr [eax+64h]
      01d4014d  pop     ebx
      01d4014e  pop     esi
      01d4014f  pop     edi
      01d40150  pop     ebp
      01d40151  ret
      

      老趙為上面這段匯編代碼添加了注釋,我們主要從打印出“Here comes the testing code.”字樣的代碼開始進行分析。值得注意的是,在調用MyArrayList或MyList<object>的get_Item方法之前,都會把這個對象的地址放置到ecx寄存器中,然后把edx寄存器清零作為get_Item方法的參數。這樣做的好處是加快訪問對象及參數的速度,如果每次都需要從線程棧上讀取這些(就像我們學習匯編時的那些經典案例),其性能肯定比不上讀取寄存器。顯然,調用Console.ReadLine靜態方法是不需要對象地址的,因此無須對ecx寄存器有所操作。

      分析get_Item方法的匯編代碼

      從Main函數的匯編代碼中我們可以獲得get_Item方法的入口。那么我們現在就來分析MyArrayList類型的get_Item方法,請注意,此時ecx寄存器保存的是MyArrayList對象的地址,edx保存了get_Item方法的參數:

      0:000> dd 343424h
      00343424  01d40168 71060003 20000006 01d40190
      00343434  fffffff8 00000004 00000001 00080000
      00343444  0000000c 00040011 00000004 5c1e061c
      00343454  00342ff8 00343478 0034141c 00000000
      00343464  00000000 5c136aa0 5c136ac0 5c136b30
      00343474  5c1a7410 00000080 00000000 003434c0
      00343484  10000002 90000000 003434c0 00000000
      00343494  0034c05c 00020520 00000004 00000004
      0:000> !u 01d40168
      Normal JIT generated code
      TestConsole.MyArrayList.get_Item(Int32)
      Begin 01d40168, size 17
      >>> 01d40168 55              push    ebp
      01d40169 8bec            mov     ebp,esp
      // 把MyArrayList對象的m_items字段地址(對象地址偏移4字節)保存至eax寄存器中
      01d4016b 8b4104          mov     eax,dword ptr [ecx+4]
      // 比較傳入的參數(edx寄存器)與數組長度(eax寄存器為數組地址,再偏移4字節)的大小
      01d4016e 3b5004          cmp     edx,dword ptr [eax+4]
      // 如果參數超過數組長度,則跳轉至錯誤處理代碼
      01d40171 7306            jae     01d40179
      // 把需要的元素地址放置到eax寄存器中
      // 從數組地址開始偏移12字節為第一個元素的地址,再偏移“下標 * 4”自然就是我們所需要的元素
      01d40173 8b44900c        mov     eax,dword ptr [eax+edx*4+0Ch]
      01d40177 5d              pop     ebp
      // 返回
      01d40178 c3              ret
      // 如果參數大于數組長度,就會跳轉到此
      01d40179 e806c2a15c      call    mscorwks!JIT_RngChkFail (5e75c384)
      01d4017e cc              int     3
      

      如果要理解上面的代碼,可能需要您再去回味文章上半段的分析。尤其是幾個偏移量:

      • MyArrayList對象偏移4字節則為m_items字段地址
      • 數組地址偏移4字節則為其長度
      • 數組地址偏移12字節為其第一個元素的地址

      然后,再結合ecx(MyArrayList對象地址),edx(參數)以及eax(保存了方法返回值)幾個寄存器的作用,相信理解上面這段代碼也并非難事。

      MyArrayList的代碼分析完了,那么MyList<object>的匯編代碼又是如何?

      0:000> dd 343594h
      00343594  01d401b8 01d401e0 00010001 003435a4
      003435a4  5c1e0670 00000000 00000000 00000080
      003435b4  00000000 fffffff8 00000004 00000001
      003435c4  00080010 0000000c 00040011 00000004
      003435d4  5c1e061c 00342ff8 00343610 0034355a
      003435e4  00343600 00000000 5c136aa0 5c136ac0
      003435f4  5c136b30 5c1a7410 00010001 00343604
      00343604  5c1e061c 00000000 00000000 00000080
      0:000> !u 01d401b8
      Normal JIT generated code
      TestConsole.MyList`1[[System.__Canon, mscorlib]].get_Item(Int32)
      Begin 01d401b8, size 17
      >>> 01d401b8 55              push    ebp
      01d401b9 8bec            mov     ebp,esp
      01d401bb 8b4104          mov     eax,dword ptr [ecx+4]
      01d401be 3b5004          cmp     edx,dword ptr [eax+4]
      01d401c1 7306            jae     01d401c9
      01d401c3 8b44900c        mov     eax,dword ptr [eax+edx*4+0Ch]
      01d401c7 5d              pop     ebp
      01d401c8 c3              ret
      01d401c9 e8b6c1a15c      call    mscorwks!JIT_RngChkFail (5e75c384)
      01d401ce cc              int     3
      

      是否發現,兩者的代碼除了幾個地址之外可以說完全一樣?

      總結

      還需要多說什么嗎?我們通過比較匯編代碼,已經證明了MyArrayList和MyList<Object>在執行時所經過的指令幾乎完全相同。到了這個地步,您是否還認為泛型會影響程序性能?

      最后繼續強調一句:老趙并不喜歡IL,更不喜歡匯編。除非萬不得已,老趙是不會往這方面去思考問題的。我們有太多東西可學,如果不是目標明確,老趙建議您還是不要投身于IL或匯編這類東西為好。

      最后附上dump文件

      主站蜘蛛池模板: 欧美成人精品手机在线| 福利一区二区不卡国产| 中文人妻av高清一区二区| 久久精品国产一区二区蜜芽| 熟女一区二区中文字幕| 国产免费一区二区三区在线观看| 欧美人妻在线一区二区| 国产乱人伦av在线无码| 久久久久人妻精品一区三寸 | 亚洲一区二区三区| 精品一区二区三区蜜桃麻豆| 国内精品一区二区在线观看| 国产亚洲精品第一综合麻豆| 国产精品国产亚洲区久久| 国产精品精品一区二区三| 隔壁老王国产在线精品| 夏河县| 日韩av熟女人妻一区二| 亚洲v欧美v日韩v国产v| 少妇无码AV无码专区| 在线日韩日本国产亚洲| 日本精品aⅴ一区二区三区| 在线观看亚洲精品国产| 亚洲精品日本一区二区| 日韩精品中文字幕第二页| 成人午夜电影福利免费| 日区中文字幕一区二区| 91九色国产成人久久精品| 人妻有码中文字幕在线| 北岛玲中文字幕人妻系列| 蜜臀人妻精品一区二区免费| 人成午夜大片免费视频77777 | 最新国产精品精品视频| 国产一区二区av天堂热| 无码日韩做暖暖大全免费不卡| 国产精品久久精品国产| 日本久久一区二区三区高清| 亚洲日韩性欧美中文字幕| 五家渠市| 狠狠躁夜夜躁无码中文字幕| 人妻人人妻a乱人伦青椒视频|