托管應(yīng)用程序的全局分配配置文件定義了垃圾回收器對(duì)與應(yīng)用程序相關(guān)的內(nèi)存進(jìn)行治理的工作量有多大。GC 治理內(nèi)存的工作量越大,GC 所經(jīng)歷的 CPU 周期數(shù)就越多,而 CPU 運(yùn)行應(yīng)用程序代碼所花費(fèi)的時(shí)間也就越短。分配配置文件由已分配對(duì)象數(shù)、對(duì)象的大小及其生命周期計(jì)算得出。緩解 GC 壓力的一種最明顯的方法就是減少分配的對(duì)象數(shù)量。使用面向?qū)ο笤O(shè)計(jì)技術(shù)將應(yīng)用程序設(shè)計(jì)為具有可擴(kuò)展性、模塊化和可復(fù)用的特性,往往會(huì)導(dǎo)致分配的數(shù)量增多。抽象和“精確”都會(huì)導(dǎo)致性能下降。
典型的實(shí)際分配配置文件介于上面提到的兩種分配配置文件之間。分配配置文件的一個(gè)重要度量標(biāo)準(zhǔn)是 CPU 花在 GC 上的時(shí)間占其總時(shí)間的百分比。您可以通過 .NET CLR Memory:% Time in GC 性能計(jì)數(shù)器獲得這一數(shù)字。假如此計(jì)數(shù)器的平均值大于 30%,則您可能需要對(duì)您的分配配置文件進(jìn)行一次仔細(xì)的檢查。這并不一定意味著您的分配配置文件有問題,在某些占用大量內(nèi)存的應(yīng)用程序中,GC 達(dá)到這種水平是必然的,也是正常的。當(dāng)您碰到性能問題時(shí),首先應(yīng)該檢查此計(jì)數(shù)器,它將立即顯示出您的分配配置文件是否出現(xiàn)了問題。
提示:假如 .NET CLR Memory:% Time in GC 性能計(jì)數(shù)器指示您的應(yīng)用程序花在 GC 上的平均時(shí)間高于它的總時(shí)間的 30%,則表明您需要對(duì)您的分配配置文件進(jìn)行一次仔細(xì)的檢查。
提示:GC 友好的應(yīng)用程序中包含的第 0 代對(duì)象遠(yuǎn)遠(yuǎn)多于第 2 代對(duì)象。此比率可以通過比較 NET CLR Memory:# Gen 0 Collections 和 NET CLR Memory:# Gen 2 Collections 性能計(jì)數(shù)器的結(jié)果來得出。 用于分析的API和CLR分析器
托管程序集是托管代碼的分發(fā)單位,它由 Microsoft 中間語言(MSIL 或 IL)構(gòu)成,適用于所有的處理器。CLR 的實(shí)時(shí) (JIT) 功能可將 IL 編譯成優(yōu)化的本機(jī) X86 指令。JIT 是一種執(zhí)行優(yōu)化操作的編譯器,但是由于編譯是在軟件運(yùn)行時(shí)進(jìn)行的,并且僅當(dāng)?shù)谝淮握{(diào)用方法時(shí)才會(huì)進(jìn)行,因此進(jìn)行優(yōu)化的次數(shù)需要與執(zhí)行編譯所花費(fèi)的時(shí)間保持平衡。通常,這對(duì)于服務(wù)器應(yīng)用程序并不重要,因?yàn)閱?dòng)時(shí)間和響應(yīng)對(duì)于它們來說通常都不構(gòu)成問題;但對(duì)于客戶端應(yīng)用程序來說,卻十分重要。請(qǐng)注重,安裝時(shí)可以通過使用 NGEN.exe 執(zhí)行編譯來加快啟動(dòng)時(shí)間。
CLR 可提供兩組不同的類型:引用類型和值類型。引用類型總是分配到托管堆中,并按引用傳遞(正如它的名稱所暗示的)。值類型分配到棧中或在堆中內(nèi)聯(lián)為對(duì)象的一部分,默認(rèn)情況下按值傳遞,不過您也可以按引用來傳遞它們。分配值類型時(shí),所需的開銷非常小,假設(shè)它們總是又小又簡單,當(dāng)它們作為參數(shù)進(jìn)行傳遞時(shí)開銷也會(huì)很小。正確使用值類型的一個(gè)很好的示例就是包含 x 和 y 坐標(biāo)的 Point 值類型。
using System.Threading; //... public class MyClass { void MyClass() //構(gòu)造函數(shù) { //以原子方式遞增全局實(shí)例計(jì)數(shù)器的值 Interlocked.Increment(ref MyClassInstanceCounter); }
反射是由 CLR 提供的一種機(jī)制,用于在運(yùn)行時(shí)通過編程方式獲得類型信息。反射在很大程度上取決于嵌入在托管程序集中的元數(shù)據(jù)。許多反射 API 都要求搜索并分析元數(shù)據(jù),這些操作的開銷都很大。
這些反射 API 可以分為三個(gè)性能區(qū)間:類型比較、成員枚舉和成員調(diào)用。這些區(qū)間的系統(tǒng)開銷一直在變大。類型比較操作,例如 C# 中的 typeof、GetType、is、IsInstanceOfType 等,都是開銷最小的反射 API,盡管它們的實(shí)際開銷一點(diǎn)也不小。成員枚舉操作可以通過編程方式對(duì)類的方法、屬性、字段、事件、構(gòu)造函數(shù)等進(jìn)行檢查。例如,可能會(huì)在設(shè)計(jì)時(shí)的方案中使用這一類的成員枚舉操作,在這種情況下,此操作將枚舉 Visual Studio 中的 Property Browser(屬性瀏覽器)的 Customs Web Controls(自定義 Web 控件)的屬性。那些用于動(dòng)態(tài)調(diào)用類成員或動(dòng)態(tài)發(fā)出 JIT 并執(zhí)行某個(gè)方法的的反射 API 是開銷最大的反射 API。當(dāng)然,假如需要?jiǎng)討B(tài)加載程序集、類型實(shí)例化以及方法調(diào)用,還存在一種后期綁定方案,但是這種松散的耦合關(guān)系需要進(jìn)行明確的性能權(quán)衡。一般情況下,應(yīng)該在對(duì)性能影響很大的代碼路徑中避免使用反射 API。請(qǐng)注重,盡管您沒有直接使用反射,但是您使用的 API 可能會(huì)使用它。因此,也要注重是否間接使用了反射 API。
提示:假如您正在使用 VB.NET,且并不一定需要后期綁定,您可以在源文件的頂部包含 Option EXPlicit On 和 Option Strict On 以便通知編譯器拒絕后期綁定。這些選項(xiàng)將強(qiáng)制您進(jìn)行聲明,并要求您設(shè)置變量類型并關(guān)閉隱式轉(zhuǎn)換。
安全性
安全性是必要的而且也是主要的 CLR 的組成部分,使用它時(shí)會(huì)降低性能。當(dāng)代碼為 Fully Trusted(完全信任)且安全策略為默認(rèn)設(shè)置時(shí),安全性對(duì)應(yīng)用程序的吞吐量和啟動(dòng)時(shí)間的影響會(huì)很小。對(duì)代碼持不完全信任態(tài)度(例如,來自 Internet 或 Intranet 區(qū)域的代碼)或縮小 MyComputer Grant Set 都將增加安全性的性能開銷。
COM 互操作和平臺(tái)調(diào)用
COM 互操作和平臺(tái)調(diào)用會(huì)以幾乎透明的方式為托管代碼提供本機(jī) API,通常調(diào)用大多數(shù)本機(jī) API 時(shí)都不需要任何非凡代碼,但是可能需要使用鼠標(biāo)進(jìn)行多次單擊。正如您所預(yù)計(jì)的,從托管代碼中調(diào)用本機(jī)代碼會(huì)帶來開銷,反之亦然。這筆開銷由兩部分組成:一部分是固定開銷,此開銷與在本機(jī)代碼和托管代碼之間進(jìn)行的轉(zhuǎn)換有關(guān);另一部分是可變開銷,此開銷與那些可能要用到的參數(shù)封送和返回值有關(guān)。COM 互操作和平臺(tái)調(diào)用的固定開銷在開銷中占的比例較小:通常不超過 50 條指令。在各托管類型之間進(jìn)行封送處理的開銷取決于它們?cè)谶吔鐑蓚?cè)的表示形式的相似程度。需要進(jìn)行大量轉(zhuǎn)換的類型開銷相對(duì)較大。例如,CLR 中的所有字符串都為 Unicode 字符串。假如要通過平臺(tái)調(diào)用需要 ANSI 字符數(shù)組的 Win32 API,則必須縮小該字符串中的每個(gè)字符。但是,假如是將托管的整數(shù)數(shù)組傳遞到需要本機(jī)整數(shù)數(shù)組的類型中時(shí),就不需要進(jìn)行封送處理。
提示:應(yīng)創(chuàng)建“小而精”的 COM 互操作和平臺(tái)調(diào)用,而不是“大而全”的調(diào)用,并確保調(diào)用的開銷對(duì)于調(diào)用的工作量是劃算的。
請(qǐng)注重,不存在與托管線程相關(guān)的線程模式。當(dāng)您打算進(jìn)行 COM 互操作調(diào)用時(shí),需要確保已將執(zhí)行調(diào)用的線程初始化為正確的 COM 線程模式。此操作通常是使用 MTAThreadAttribute 和 STAThreadAttribute 來實(shí)現(xiàn)的(盡管也可以通過編程來實(shí)現(xiàn))。