做爲 .Net 攻城師,所必需掌握的 .Net Profiling 技術

衆所周知,性能問題是全部實用應用在迭代過程當中必然要面對的問題。對於此類問題,簡單地投入更多硬件資源的作法可能會取得必定效果。但總的來看,此類作法的邊際成本是不斷上升的。換言之,隨着性能需求的上漲,要換取一樣的性能提高,僅憑硬件升級所須要的成本會愈來愈高。故而性能優化是每一位運維/軟件開發人員必須掌握的技術。html

.Net Profiling

在進行應用性能優化實踐時,首先面對的就是熱點定位,即肯定那些帶來巨大資源耗散的代碼位置。而在不借助外部工具的前提下,定位資源熱點是一件至關困難的事。它須要當事人對於應用實現自己有一個總體的把握,瞭解應用架構內每個功能模塊代碼的路徑與細節。與此同時,當事人還要對於應用實現所依賴的第三方功能庫的表現有必定的把握。對於那些具有必定規模的應用系統,具有前述素質的工程師的數量屈指可數。而即使是這些鳳毛麟角的優秀人才,其熱點預估也不能保證必定是準確的。瀏覽器

Profiling 技術) 的提出正是爲了解決熱點定位而提出的,它以程序的實際運行數據來幫助工程師們來理解應用行爲,極大地簡化了工程師們的工做。而對於像 .Net 這樣比較成熟的技術棧,專家們(如 Bill Chiles趙頡)都推薦工程人員經過實際的 Profiling 數據來定位性能瓶頸。實際操做中,獲取 .Net Profiling 數據的功能被實現爲一個被稱爲 Profiler 的 COM 組件實體。下文中,咱們將就 Profiler 自己的實現進行一些探討。安全

Profiling API

.Net Profiler 在本質上是 CLR.aspx) 的一個插件,它經過應用 Profiling API.aspx) 來保持與 CLR 的信息溝通,並以此獲取到 .Net 應用的運行時數據。一般來講,Profiler 的實體都表現爲一個動態連接庫(即 .dll 文件),CLR 在運行時會去加載該庫(CLR 加載 Profiler 的詳細配置能夠參考 這篇文檔.aspx)),並在程序運行的特定階段向庫發送信息並接受庫所返回的信息。性能優化

須要特別強調的一點是,雖然被稱做是 Profiling API,但 CLR 的這套接口能作的可不只僅是簡單地度量應用的運行時間和內存耗散。實踐上,profiling API 可以完成諸如代碼覆蓋、運行時插入等許多高級功能。不過,正如 MSDN Profiling 綜述文檔.aspx) 所強調的,Profiling 對於應用自己應該透明。也就是說,在編寫應用的時候,開發人員不該該在本身的邏輯中依賴 Profiler 或者被其影響。數據結構

對於 .Net 技術棧而言,因爲環境自己引入了 application domain.aspx), GC, managed exception handling, JIT 等高級特性,Profiling 所展示的就不能僅止於應用運行所消耗的時間或者內存。爲了可以真正地表現出運行時行爲,Profiling API 中提供了包含這些特性的數據的接口。這就使得 Profiling API 在設計並非如不少人想得那樣直觀。多線程

常見的 .Net Profiler 實現多采起以下架構:

.Net Profiler 架構架構

其中,ICorProfilerCallback.aspx) 與 ICorProfilerInfo.aspx) 就是 Profiling API 中最常被應用到的兩個。在應用運行時,Profiler DLL 會被加載到應用所在的進程中。經過實現 ICorProfilerCallback 下特定功能的接口,Profiler DLL 會在應用運行時收到相關的動做執行通知。例如,若是在 Profiler DLL 中實現了 ICorProfilerCallback::AssemblyLoadFinished 接口,那麼在應用運行中每加載完一個 程序集.aspx) 時,Profiler DLL 中該接口的實現代碼就會被調用。與此相似,Profiler DLL 能夠靠實現 ICorProfilerInfo 下的接口來完成對被監測應用狀態的獲取。併發

須要補充說明一點,上文所說的 ICorProfilerCallback 接口實際上存在有 ICorProfilerCallback ~ ICorProfilerCallback7 這樣7個版本的接口定義。高標號的接口版本向下兼容,但會提供新的功能擴展。不過,更高標號的接口每每也須要有更新版本的 CLR 來支持(如調用 ICorProfilerCallback7 須要在環境中部署 .Net Framework 4.6.1 以上版本),在實際使用時須要多加註意。app

目前,Profiling API 能夠被任何非託管的 COM 兼容的語言所調用。另外,API 自己的實現很是高效的,不會帶來大到足以致使 profiling 失效的額外性能負擔。也正所以,基於 Profiling API 徹底能夠實現一個抽樣 profiler(對於 profiling 模式的探討可參照 這篇文獻 的 2.1 章節內容)。運維

目前 Profiling API 所支持的特性

正如前文所述,Profiler 對於程序行爲的描述源自 profiling API 所提供的信息。在目前版本中,憑藉 profiling API 可以獲取到下列事件的消息通知:

  1. CLR 的啓動與關停

  2. application domain 的建立與關閉

  3. 程序集的加載與卸載

  4. 模塊(Module)的加載與卸載

  5. COM vtable 的建立與銷燬

  6. JIT 編譯與 code-pitching 的出發

  7. 類的加載卸載

  8. 線程的建立與銷燬

  9. 函數的進入與返回

  10. 託管代碼與非託管代碼的執行切換

  11. 運行時掛起

  12. 運行時堆內存信息與 GC 活動

隨着 .Net 技術的演進,將來的 Profiling API 或許可以提供更多的信息。不過,如下功能點是 Profiling API 不會實現的,請在應用時迴避:

  1. 非託管代碼的執行信息

  2. 運行時修改自身代碼的應用的 Profiling(如 AOP)

  3. 邊界檢驗

  4. 遠程 profiling

  5. 高可靠性環境下的 profiling

線程相關

對於加載了 Profiler DLL 的進程而言,其在建立新線程時,新線程自己也會產生 ICorProfilerCallback 接口下定義的各類事件通知。這一過程當中,Profiler 沒必要去顯式地指定一個 ThreadID 以使得 Profiling API 生效。一樣的,Profiler 徹底能夠簡單地在代碼中使用 thread-local 的存儲方式,用不着費心地去進行存儲位置的全局重定向。

固然,仍是有一些要點須要咱們在併發背景下留意。例如,Profiling API 自己並不能保障數據結構的線程安全,所以咱們須要在可能產生並行訪問的地方給 Profiler 代碼衝突區加鎖以保證 Profiler 的行爲符合預期。一樣,對於多線程的應用場景來講,Profiler 不該該假設 ICorProfilerCallback 的各個接口存在必定的前後順序。舉例來講,一個有兩個一樣線程的程序在運行時可能會先產生一個 FunctionEnter 而後才產生 ICorProfilerCallback::JITCompilationFinished。

還有一個線程相關的問題是來自於 COM 接口的。上文中咱們說過 Profiler 事實上是實現爲一個 COM 組件的,但其實 CLR 在運行時並不會去初始化 COM。這是爲了不在應用代碼指定線程模型前,CLR 調用 CoInitialize 來指定應用線程模型。一樣地,在 Profiler 內部,不要去調用 CoInitialize 以免與應用代碼產生衝突。

調用棧

獲取調用棧信息是應用 Profiling 時的一項關鍵需求。針對這一需求,Profiling API 提供給 Profiler 編寫者兩種實現方式:棧快照和倒影棧。

棧快照是在特定時刻對特定線程調用棧的一次追蹤。須要注意,Profiling API 僅支持對棧上託管函數的追蹤。若是 Profiler 須要追蹤棧上的非託管函數,則須要本身提供一個棧遍歷器出來。讀者如對 Profiler 棧快照機制的實現詳情感興趣,能夠參照 Divid Broman 的 這篇博客 內容。

頻繁使用棧快照會爲 CLR 帶來過多的額外性能損耗。所以,若是須要頻繁進行棧追蹤,那麼 Profiler 應該經過 FunctionEnter2.aspx), FunctionLeave2.aspx), FunctionTailCall2.aspx) 及 ICorProfilerCallback::Exception* 等一系列接口構建出當前應用調用棧的一個倒影。如此,Profiler 便可低消耗地進行棧追蹤操做。

其餘須要留意之處

前文強調過,Profiler 是一個非託管的 DLL 庫,會在應用運行時被加載到 CLR 中並與應用處於同一進程空間下。如此,Profiler DLL 實質上是不受託管代碼的訪問控制的。其運行惟一的限制就是運行 Profiler 的 OS 用戶必須擁有足夠權限。所以,對於要部署 Profiler 的技術人員來講,必需要明白這其中可能的風險,提前進行準備。例如能夠把 Profiler DLL 加到訪問控制列表(ACL)中以避免惡意用戶對其加以利用。

還有,Profiler DLL 做爲 CLR 的一個插件,其運行錯誤可能會引發 CLR 自己的崩潰,在實施時必定要足夠當心。而對於那些運行在棧內存空間緊張的環境下的 Profiler,要警戒由於 ICorProfilerCallback 致使棧溢出而引發的應用崩潰。在這種資源受限的環境中,要儘量地減小 Profiler 自身的資源耗散。盡力避免本來可以運行的應用由於 Profiler 而致使沒法運行的狀況。

結語

本文簡述了 .Net Profiling 技術的整體狀況,並就其中的一些重要技術點進行了闡述。但願能幫助讀者初步理解 .Net Profiling 技術。後續,咱們將具體到代碼實施層面,就 .Net Profiling 的實現進行詳細討論

OneAPM 助您輕鬆鎖定 .NET 應用性能瓶頸,經過強大的 Trace 記錄逐層分析,直至鎖定行級問題代碼。以用戶角度展現系統響應速度,以地域和瀏覽器維度統計用戶使用狀況。想閱讀更多技術文章,請訪問 OneAPM 官方博客
本文轉自 OneAPM 官方博客

相關文章
相關標籤/搜索