[譯]怎樣用VisualStudio查看Unmanaged 程式碼

來源:互聯網
上載者:User

(譯者:這篇文章作者是一位美國的MVP,這是他的系列文章"Under the cover"的第一篇,文章的本意從最底層的角度來最佳化代碼的效能,並作為閱讀作者其他文章的技術基礎,這種通過這樣的做法雖然初看起來有些過分,但是對讀者瞭解.Net許多底層運作是十分有益的)

我們從使用visual studio進行Unmanaged 程式碼調試的基礎開始,以便大家可以更容易的學習今後的例子,並讓這篇文章作為我以後文章的基礎,雖然我也使用windbg,但visual studio已經成為了一個功能強大的調試工具,對於簡單的代碼最佳化問題反而更容易使用

當我們需要校調對效能要求很高的代碼時,查看IL通常不是最好的做法,因為JIT最佳化器會默默的最佳化我們的代碼,使用reflector或者ildasm你能很快發現release和debug模式下產生的IL代碼幾乎完全相同,那麼是什麼讓release模式的代碼運行起來如此迅速呢?這就是JIT最佳化的結果,通過查看managed代碼(IL代碼),我們沒有辦法看到這些最佳化,所以我們將通過native code(本地代碼)來尋找蛛絲馬跡。

必須說明我不提倡大家經常這樣做,我不贊成過早的進行最佳化,你必須使你的代碼先工作起來,你必須清楚的知道哪些代碼是不值得最佳化的,當你的程式碼完成後再來找那些需要提速的地方,當你發現有的地方10% 的代碼卻使用了70%的時間的時候,再回過頭去最佳化那10%的代碼.同時你總是應該把判斷的依據建立在對速度的實際測量上,而非僅僅是閱讀代碼,最後,其實資料結構的選擇比底層的最佳化重要的多

當然話又說回來,瞭解隱藏在.Net底層的秘密是非常有趣的,那就讓我們開始設定visualstudio,並動手實驗一個簡單的例子

首先我們需要一些實驗代碼

        static void Main(string[] args) {

            for (int i = 0; i < 10; i++) {

                Console.WriteLine("Hello World!");

            }

        }

為了開啟Unmanaged 程式碼調試,我們需要對visual studio進行設定.開啟項目的屬性並進入Debug Tab,選擇該頁上的“Enable unmanaged code debugging”複選框

(注意,這個選項只對當前使用的配置有效,因此我們應該為我們使用的所有配置設定這個選項.)在迴圈的開始處插入一個斷點,並運行程式,你將會像往常一樣擊中一個斷點。這時你的螢幕應該看起來二(譯者:原文缺圖)如果你沒有stack視窗,可以通過menu -> windows -> call stack (或者  ctrl + d  c)將其呼出,開啟call stack後,我們就可以通過右擊滑鼠,選擇go to disassembly進入下面的代碼
    

static void Main(string[] args) {00000000  push        ebp00000001  mov         ebp,esp00000003  push        edi00000004  push        esi00000005  push        ebx00000006  sub         esp,38h00000009  xor         eax,eax0000000b  mov         dword ptr [ebp-10h],eax0000000e  xor         eax,eax00000010  mov         dword ptr [ebp-1Ch],eax00000013  mov         dword ptr [ebp-3Ch],ecx00000016  cmp         dword ptr ds:[00912DC8h],00000001d  je          000000240000001f  call        792B228E00000024  xor         esi,esi00000026  xor         edi,edi00000028  nopfor (int i = 0; i < 10; i++) {00000029  xor         esi,esi0000002b  nop0000002c  jmp         0000003D0000002e  nopConsole.WriteLine("Hello World!");0000002f  mov         ecx,dword ptr ds:[022B303Ch]00000035  call        785D90740000003a  nop}0000003b  nopfor (int i = 0; i < 10; i++) {0000003c  inc         esi0000003d  cmp         esi,0Ah00000040  setl        al00000043  movzx       eax,al00000046  mov         edi,eax00000048  test        edi,edi0000004a  jne         0000002E}0000004c  nop0000004d  lea         esp,[ebp-0Ch]00000050  pop         ebx00000051  pop         esi00000052  pop         edi00000053  pop

 我們正在查看的就是JIT為我們的代碼產生的native code(本地代碼),我們可以看到簡單的迴圈在native code層次上怎麼啟動並執行,如果你從來沒有研究過native code,這些本來很普通的代碼可能看起來相當的奇怪,讓我們來自己看看這裡發生了什麼

00000029  xor         esi,esi

0000002b  nop             

0000002c  jmp         0000003D

上面代碼初始化我們在ESI中的計數器,ESI是一個索引寄存器,可以用來索引數組,你可以看到這裡用了一個很古老的"把戲"來把計數器清0,代碼沒有使用把0值放入寄存器,而是讓寄存器自己異或(xor)自己來達到清0的目的,接下來的一行Nop,意思是"沒有操作",而他們的作用就和他們的名字一樣,什麼也不做,代碼接下來立即跳轉到3D.有時候像這樣的跳轉使得我們的代碼不是自上而下的運行(就象許多進階語言比如c,vb,c#裡面一樣),如果跟著這個跳轉進入這個迴圈的另外一個部分,就可以繼續分析我們的代碼

0000003c  inc         esi 

0000003c後面第一個指令把ESI中的計數器加一(通過register視窗或者按鍵組合ctrl+D R  你可以看到它的值),在第一次迴圈時代碼會跳過這行,因為上面的跳轉指令直接指向了0000003D

0000003d  cmp         esi,0Ah

00000040  setl        al  

00000043  movzx       eax,al

00000046  mov         edi,eax

00000048  test        edi,edi

0000004a  jne         0000002E

從0000003D開始到4a,代表於迴圈停止值的實際比較和跳轉如果我們沒有達到這個值(i<10),最後一行會跳轉到2e(譯者注:原著這裡為4a,是個筆誤)繼續這個迴圈,也就是迴圈體開始的地方
0000002f  mov         ecx,dword ptr ds:[022B303Ch]

00000035  call        785D9074

上面的第一條行將會把字串從從記憶體裝在到ECX 寄存器 (這是一個通用寄存器), 一般ECX總是用作把第一個參數傳給方法,在執行個體的方法中,ECX將總是包含this,緊接著是包含第二個參數的EDX,然後是一系列的push,用於把其他參數入棧

下一條語句執行實際的調用。我們待會再來探討怎麼去尋找所調用的方法,但是現在我們可以從VisualStudio給出的原始碼看到,這毫無疑問就是 Console.WriteLine ,代碼接著執行索引的自增,並返回來繼續執行loop迴圈內部的代碼

 

然而,我們的微不足道的例子中已經產生產生了明顯的浪費。下面是一個例子
00000009  xor         eax,eax

0000000b  mov         dword ptr [ebp-10h],eax

0000000e  xor         eax,eax

00000010  mov         dword ptr [ebp-1Ch],eax

我們在一行裡兩次對EAX置0,這時因為我們正運行在debug模式下,偵錯模式下是不進行最佳化的,換句話說,這段代碼只是被JIT執行,但是卻沒有允許JIT作任何智能最佳化

下面讓我們來看看經過最佳化的代碼:

這裡有一些關於查看最佳化代碼的問題
1)是調試器預設關閉了JIT的最佳化(我自己就曾經在大半夜花了很長時間才意識到自己一直在看沒有被最佳化的代碼)
 2)是必須處理"Just My Code"選項對最佳化代碼的影響

我最初在Vance Morrison的文章上看到瞭解決這個問題的辦法(謝謝 Vance,我已經被整個問題困擾了很長一段時間,並最終使用直接查看沒有源碼的原始assemble的方式).

要搞定這個問題,清跟著以下的步驟作

1) 開啟 Tools -> Options -> Debugging -> General 

2)確保 ‘Suppress JIT optimization on module load’沒有被選中

3)也確保‘Enable Just My Code’沒有被選中

Vance 也建議我們進入advanced build設定release dll為pdb only,這時我們可以用前面同樣的方式運行這段代碼

用 JIT 看我們的代碼另外一種方式是使用release模式,使用Start the executable without the debugger,再附加visualstudio到進程進行調試.

使用任一方法我們都能讓 JIT 將代碼最佳化了。得到最佳化的代碼如下

         for (int i = 0; i < 10; i++) {00000000  push        esi00000001  xor         esi,esiConsole.WriteLine("Hello World!");00000003  cmp         dword ptr ds:[02271084h],00000000a  jne         000000160000000c  mov         ecx,100000011  call        786FC65400000016  mov         ecx,dword ptr ds:[02271084h]0000001c  mov         edx,dword ptr ds:[0227307Ch]00000022  mov         eax,dword ptr [ecx]00000024  call        dword ptr [eax+000000D8h]for (int i = 0; i < 10; i++) {0000002a  add         esi,10000002d  cmp         esi,0Ah00000030  jl          0000000300000032  pop         esi}}00000033  ret

哇,這次的代碼比第一次少多了,JIT 最佳化確實工作的很好,這就是為什麼查看實際的反編譯代碼而非IL是這樣重要,因為JIT經常會通過識別IL中的模式來進行最佳化,機敏的讀者可能注意在我們的迴圈的內部事實上產生了更多的密碼。初看起來這是非常可怕的,但其實這說明最佳化器已經協助我們inline了Console.WriteLine方法,實際是節省了很多代碼在接下來的文章中我會談到inline,但是大家先明白這是一個很重要的最佳化

我們這時已經準備好了怎樣在調試器中去欣賞最佳化的和沒有最佳化的代碼,我想這是一個好的開始,下面的幾個文章我會為更深入的瞭解JIT的一般最佳化過程而打好基礎,我們也可以接觸一些工具,看看他們會怎樣協助我們得到更好的代碼

 希望能在那裡見你。

原文地址

http://codebetter.com/blogs/gregyoung/archive/2006/06/09/146298.aspx

原文的一些資源:

http://en.wikipedia.org/wiki/X86 

http://www.codeguru.com/csharp/.net/net_general/il/article.php/c4635/ IL tutorial

http://burks.brighton.ac.uk/burks/language/asm/asmtut/asm1.htm ASM tutorial  

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.