關於MSIL中的Nop的問題?
由於項目需要,在寫一個動態代理的東西,需要用Emit動態構造類型,基本已經寫完了,但是將自己整的ilgenerator和手寫C#code ildasm反彙編出來的比對一下,發現C#編譯成的ildasm有很多nop指令,想詳細的了解一下這個指令對程序運行的性能有什麼影響(我只知道它是補齊操作碼啥的,很泛泛,想具體了解一下細節和為啥要用它的原因以及什麼情況下用和不用的影響等等)~謝謝百忙之中抽出時間解答的各位~
請用release模式編譯,您會發現那些nop就沒了。那些nop是跟csc與CLR配合實現的代碼可調試性相關的。
藉助Roslyn的實現可以一窺這些nop生成的邏輯都是在幹什麼。例如說,
roslyn/CodeGenerator.cs at 8fdc0214f73fd513e5b513573f8f916e02362961 · dotnet/roslyn · GitHub if (instructionsEmitted == 0 syntax != null _optimizations == OptimizationLevel.Debug)
{
// if there was no code emitted, then emit nop
// otherwise this point could get associated with some random statement, possibly in a wrong scope
_builder.EmitOpCode(ILOpCode.Nop);
}
if (scopeType == ScopeType.TryCatchFinally IsJustPastLabel())
{
DefineHiddenSequencePoint();
EmitOpCode(ILOpCode.Nop);
}
樓上說的都對,我如果沒記錯的話,根本性的原因在於斷點的實現原理:彙編指令int 3。
也就是說,當你決定設置一個斷點的時候,內存里的代碼區域需要能找到一個對應的地方,把那裡的位元組改寫成int 3。之所以是改寫而不是插入,是因為插入會挪動之後的數據,這個不僅會有內存操作方面的巨大開銷,也有難以逾越的技術障礙(地址操作數的更新)。
.NET的Sequence Point則考慮了更多的東西,例如執行到斷點時,runtime不會處於GC中,Jitter也沒有在幹活。
在正常代碼里穿插Nop指令就相當於排版的時候留了空白,方便後來人寫批註。
你手工生成的IL並不需要Nop,畢竟這些IL並沒有對應的源代碼文件可以拿來進行源碼級調試。
像@Yu Ke 說的一樣, 對於普通的user基本上就是給你設置breakpoint, 可以達到比如設置短點在括弧這種事情. 但是還有一個重要的原因, 如果你是VS debugger developer, 你一定會用到. 這個就是nop可以被看做GC safe point. 這種的話對於VS或者.Net debugger來講可以實現很多fancy的功能, 比如使用funceval, 還有Intellirace的一些功能. 舉個例子, 比如debugger想在debugging的時候讓debuggee load一個assmebly或者做一些特殊的事情比如funceval. 在dbg build的時候,這個並不困難, 但在release build的時候, 就很麻煩, 因為很多時候sequence point不一定是GC safe. 這個時候你找到nop, 就可以完成這些事情. 當然這個例子比較極端, 只是開一下腦洞而已.
沒研究過.net的nop,不過我之前給一個產品的內置語言寫過一個調試器,倒是塞了不少nop指令,就是用來調試用. 比如想讓斷點斷在花括弧上--花括弧不是實際代碼,沒有對應指令,怎麼辦呢, 編譯器就在那裡塞個nop. 在編譯的時候生成一個指令到源代碼行列的表,這樣設了斷點我就知道斷在哪條指令上了.
基本上就是RednaxelaFX巨巨說的一樣了。要肉眼看IL的話,還是用Release編譯才比較清晰,Debug模式會插入其他東西,這些東西都是便於VS調試使用的。
推薦閱讀:
※xamarin 怎麼發音?
※快速開發桌面程序用什麼技術合適?
※async/await非同步模型是否優於stackful coroutine模型?
※學習 ASP.NET MVC 框架有什麼好的視頻教程或書籍?
※目前看來 ASP.NET 中的 Razor (CSHTML) 語言是雞肋還是奇葩?