原文引自:
http://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/SCArchDeGuide/Chapter1Introduction.mspx
第 8 章 — 智能客戶端應(yīng)用程序性能
發(fā)布日期: 08/20/2004 | 更新日期: 08/20/2004
本頁內(nèi)容
智能客戶端應(yīng)用程序可以提供比 Web 應(yīng)用程序更豐富和響應(yīng)速度更快的用戶界面,并且可以利用本地系統(tǒng)資源。如果應(yīng)用程序的大部分駐留在用戶的計(jì)算機(jī)上,則應(yīng)用程序不需要到 Web 服務(wù)器的持續(xù)的往返行程。這有利于提高性能和響應(yīng)性。然而,要實(shí)現(xiàn)智能客戶端應(yīng)用程序的全部潛能,您應(yīng)該在應(yīng)用程序的設(shè)計(jì)階段仔細(xì)考慮性能問題。通過在規(guī)劃和設(shè)計(jì)您的應(yīng)用程序時(shí)解決性能問題,可以幫助您及早控制成本,并減小以后陷入性能問題的可能性。
注改善智能客戶端應(yīng)用程序的性能并不僅限于應(yīng)用程序設(shè)計(jì)問題。您可以在整個(gè)應(yīng)用程序生存期中采取許多個(gè)步驟來使 .NET 代碼具有更高的性能。雖然 .NET 公共語言運(yùn)行庫 (CLR) 在執(zhí)行代碼方面非常有效,但您可以使用多種技術(shù)來提高代碼的性能,并防止在代碼級(jí)引入性能問題。有關(guān)這些問題的詳細(xì)信息,請參閱http://msdn.microsoft.com/perf。
在應(yīng)用程序的設(shè)計(jì)中定義現(xiàn)實(shí)的性能要求并識(shí)別潛在的問題顯然是重要的,但是性能問題通常只在編寫代碼之后對其進(jìn)行測試時(shí)出現(xiàn)。在這種情況下,您可以使用一些工具和技術(shù)來跟蹤性能問題。
本章分析如何設(shè)計(jì)和調(diào)整您的智能客戶端應(yīng)用程序以獲得最佳性能。它討論了許多設(shè)計(jì)和體系結(jié)構(gòu)問題(包括線程處理和緩存注意事項(xiàng)),并且分析了如何增強(qiáng)應(yīng)用程序的 Windows 窗體部分的性能。本章還介紹了您可以用來跟蹤和診斷智能客戶端應(yīng)用程序性能問題的一些技術(shù)和工具。
針對性能進(jìn)行設(shè)計(jì)
您可以在應(yīng)用程序設(shè)計(jì)或體系結(jié)構(gòu)級(jí)完成許多工作,以確保智能客戶端應(yīng)用程序具有良好的性能。您應(yīng)該確保在設(shè)計(jì)階段盡可能早地制定現(xiàn)實(shí)的且可度量的性能目標(biāo),以便評(píng)估設(shè)計(jì)折衷,并且提供最劃算的方法來解決性能問題。只要可能,性能目標(biāo)就應(yīng)該基于實(shí)際的用戶和業(yè)務(wù)要求,因?yàn)檫@些要求受到應(yīng)用程序所處的操作環(huán)境的強(qiáng)烈影響。性能建模是一種結(jié)構(gòu)化的且可重復(fù)的過程,您可以使用該過程來管理您的應(yīng)用程序并確保其實(shí)現(xiàn)性能目標(biāo)。有關(guān)詳細(xì)信息,請參閱 Improving .NET Application Performance and Scalability 中的第 2 章“Performance Modeling”,網(wǎng)址為:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt02.asp。
智能客戶端通常是較大的分布式應(yīng)用程序的組成部分。很重要的一點(diǎn)是在完整應(yīng)用程序的上下文中考慮智能客戶端應(yīng)用程序的性能,包括該客戶端應(yīng)用程序使用的所有位于網(wǎng)絡(luò)中的資源。微調(diào)并優(yōu)化應(yīng)用程序中的每一個(gè)組件通常是不必要或不可能的。相反,性能調(diào)整應(yīng)該基于優(yōu)先級(jí)、時(shí)間、預(yù)算約束和風(fēng)險(xiǎn)。一味地追求高性能通常并不是一種劃算的策略。
智能客戶端還將需要與用戶計(jì)算機(jī)上的其他應(yīng)用程序共存。當(dāng)您設(shè)計(jì)智能客戶端應(yīng)用程序時(shí),您應(yīng)該考慮到您的應(yīng)用程序?qū)⑿枰c客戶端計(jì)算機(jī)上的其他應(yīng)用程序共享系統(tǒng)資源,例如,內(nèi)存、CPU 時(shí)間和網(wǎng)絡(luò)利用率。
注有關(guān)設(shè)計(jì)可伸縮的高性能遠(yuǎn)程服務(wù)的信息,請參閱Improving .NET Performance and Scalability,網(wǎng)址為:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenet.asp。本指南包含有關(guān)如何優(yōu)化 .NET 代碼以獲得最佳性能的詳細(xì)信息。
要設(shè)計(jì)高性能的智能客戶端,請考慮下列事項(xiàng):
? |
在適當(dāng)?shù)奈恢镁彺鏀?shù)據(jù)。數(shù)據(jù)緩存可以顯著改善智能客戶端應(yīng)用程序的性能,使您可以在本地使用數(shù)據(jù),而不必經(jīng)常從網(wǎng)絡(luò)檢索數(shù)據(jù)。但是,敏感數(shù)據(jù)或頻繁更改的數(shù)據(jù)通常不適合進(jìn)行緩存。 |
? |
優(yōu)化網(wǎng)絡(luò)通訊。如果通過“健談的”接口與遠(yuǎn)程層服務(wù)進(jìn)行通訊,并且借助于多個(gè)請求/響應(yīng)往返行程來執(zhí)行單個(gè)邏輯操作,則可能消耗系統(tǒng)和網(wǎng)絡(luò)資源,從而導(dǎo)致低劣的應(yīng)用程序性能。 |
? |
有效地使用線程。如果您使用用戶界面 (UI) 線程執(zhí)行阻塞 I/O 綁定調(diào)用,則 UI 似乎不對用戶作出響應(yīng)。因?yàn)閯?chuàng)建和關(guān)閉線程需要系統(tǒng)開銷,所以創(chuàng)建大量不必要的線程可能導(dǎo)致低劣的性能。 |
? |
有效地使用事務(wù)。如果客戶端具有本地?cái)?shù)據(jù),則使用原子事務(wù)可幫助您確保該數(shù)據(jù)是一致的。因?yàn)閿?shù)據(jù)是本地的,所以事務(wù)也是本地的而不是分布式的。對于脫機(jī)工作的智能客戶端而言,對本地?cái)?shù)據(jù)進(jìn)行的任何更改都是暫時(shí)的。客戶端在重新聯(lián)機(jī)時(shí)需要同步更改。對于非本地?cái)?shù)據(jù)而言,在某些情況下可以使用分布式事務(wù)(例如,當(dāng)服務(wù)位于具有良好連接性的同一物理位置并且服務(wù)支持它時(shí))。諸如 Web 服務(wù)和消息隊(duì)列之類的服務(wù)不支持分布式事務(wù)。 |
? |
優(yōu)化應(yīng)用程序啟動(dòng)時(shí)間。較短的應(yīng)用程序啟動(dòng)時(shí)間使用戶可以更為迅速地開始與應(yīng)用程序交互,從而使用戶立刻對應(yīng)用程序的性能和可用性產(chǎn)生好感。應(yīng)該對您的應(yīng)用程序進(jìn)行適當(dāng)?shù)脑O(shè)計(jì),以便在應(yīng)用程序啟動(dòng)時(shí)僅加載那些必需的程序集。因?yàn)榧虞d每個(gè)程序集都會(huì)引起性能開銷,所以請避免使用大量程序集。 |
? |
有效地管理可用資源。低劣的設(shè)計(jì)決策(例如,實(shí)現(xiàn)不必要的完成器,未能在 Dispose 方法中取消終止,或者未能釋放非托管資源)可能導(dǎo)致在回收資源時(shí)發(fā)生不必要的延遲,并且可能造成使應(yīng)用程序性能降低的資源泄漏。如果應(yīng)用程序未能正確地釋放資源,或者應(yīng)用程序顯式強(qiáng)制進(jìn)行垃圾回收,則可能會(huì)妨礙 CLR 有效地管理內(nèi)存。 |
? |
優(yōu)化 Windows 窗體性能。智能客戶端應(yīng)用程序依靠 Windows 窗體來提供內(nèi)容豐富且響應(yīng)迅速的用戶界面.您可以使用多種技術(shù)來確保 Windows 窗體提供最佳性能。這些技術(shù)包括降低用戶界面的復(fù)雜性,以及避免同時(shí)加載大量數(shù)據(jù)。 |
在許多情況下,從用戶角度感受到的應(yīng)用程序性能起碼與應(yīng)用程序的實(shí)際性能同樣重要。您可以通過對設(shè)計(jì)進(jìn)行某些特定的更改來創(chuàng)建在用戶看來性能高得多的應(yīng)用程序,例如:使用后臺(tái)異步處理(以使 UI 能作出響應(yīng));顯示進(jìn)度欄以指示任務(wù)的進(jìn)度;提供相應(yīng)的選項(xiàng)以便用戶取消長期運(yùn)行的任務(wù)。
本節(jié)將專門詳細(xì)討論這些問題。
數(shù)據(jù)緩存原則
緩存是一種能夠改善應(yīng)用程序性能并提供響應(yīng)迅速的用戶界面的重要技術(shù)。您應(yīng)該考慮下列選項(xiàng):
? |
緩存頻繁檢索的數(shù)據(jù)以減少往返行程。如果您的應(yīng)用程序必須頻繁地與網(wǎng)絡(luò)服務(wù)交互以檢索數(shù)據(jù),則應(yīng)該考慮在客戶端緩存數(shù)據(jù),從而減少通過網(wǎng)絡(luò)重復(fù)獲取數(shù)據(jù)的需要。這可以極大地提高性能,提供對數(shù)據(jù)的近乎即時(shí)的訪問,并且消除了可能對智能客戶端應(yīng)用程序性能造成不利影響的網(wǎng)絡(luò)延遲和中斷風(fēng)險(xiǎn)。 |
? |
緩存只讀引用數(shù)據(jù)。只讀引用數(shù)據(jù)通常是理想的緩存對象。此類數(shù)據(jù)用于提供進(jìn)行驗(yàn)證和用戶界面顯示所需的數(shù)據(jù),例如,產(chǎn)品說明、ID 等等。因?yàn)榭蛻舳藷o法更改此類數(shù)據(jù),所以通常可以在客戶端緩存它而無須進(jìn)行任何進(jìn)一步的特殊處理。 |
? |
緩存要發(fā)送給位于網(wǎng)絡(luò)上的服務(wù)的數(shù)據(jù)。您應(yīng)該考慮緩存要發(fā)送給位于網(wǎng)絡(luò)上的服務(wù)的數(shù)據(jù)。例如,如果您的應(yīng)用程序允許用戶輸入由在多個(gè)窗體中收集的一些離散數(shù)據(jù)項(xiàng)組成的定單信息,則請考慮允許用戶輸入全部數(shù)據(jù),然后在輸入過程的結(jié)尾在一個(gè)網(wǎng)絡(luò)調(diào)用中發(fā)送定單信息。 |
? |
盡量少地緩存高度不穩(wěn)定的數(shù)據(jù)。在緩存任何不穩(wěn)定的數(shù)據(jù)之前,您需要考慮在其變得陳舊或者由于其他原因變得不可用之前,能夠?qū)⑵渚彺娑嚅L時(shí)間。如果數(shù)據(jù)高度不穩(wěn)定并且您的應(yīng)用程序依賴于最新信息,則或許只能將數(shù)據(jù)緩存很短一段時(shí)間(如果可以緩存)。 |
? |
盡量少地緩存敏感數(shù)據(jù)。您應(yīng)該避免在客戶端上緩存敏感數(shù)據(jù),因?yàn)樵诖蠖鄶?shù)情況下,您無法保證客戶端的物理安全。但是,如果您必須在客戶端上緩存敏感數(shù)據(jù),則您通常將需要加密數(shù)據(jù),該操作本身也會(huì)影響性能。 |
有關(guān)數(shù)據(jù)緩存的其他問題的詳細(xì)信息,請參閱本指南的第 2 章。另請參閱 Improving .NET Application Performance and Scalability 的第 3 章“Design Guidelines for Application Performance”(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt03.asp) 的“Caching”一節(jié)以及 Improving .NET Application Performance and Scalability 的第 4 章“Architecture and Design Review of .NET Application for Performance and Scalability”(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt04.asp)。
網(wǎng)絡(luò)通訊原則
您將面臨的另一個(gè)決策是如何設(shè)計(jì)和使用網(wǎng)絡(luò)服務(wù),例如,Web 服務(wù)。特別地,您應(yīng)該考慮與網(wǎng)絡(luò)服務(wù)交互的粒度、同步性和頻率。要獲得最佳的性能和可伸縮性,您應(yīng)該在單個(gè)調(diào)用中發(fā)送更多的數(shù)據(jù),而不是在多個(gè)調(diào)用中發(fā)送較少量的數(shù)據(jù)。例如,如果您的應(yīng)用程序允許用戶在定單中輸入多個(gè)項(xiàng),則較好的做法是為所有項(xiàng)收集數(shù)據(jù),然后將完成的采購定單一次性發(fā)送給服務(wù),而不是在多個(gè)調(diào)用中發(fā)送單個(gè)項(xiàng)的詳細(xì)信息。除了降低與進(jìn)行大量網(wǎng)絡(luò)調(diào)用相關(guān)聯(lián)的系統(tǒng)開銷以外,這還可以減少服務(wù)和/或客戶端內(nèi)的復(fù)雜狀態(tài)管理的需要。
應(yīng)該將您的智能客戶端應(yīng)用程序設(shè)計(jì)為盡可能地使用異步通訊,因?yàn)檫@將有助于使用戶界面快速響應(yīng)以及并行執(zhí)行任務(wù)。有關(guān)如何使用 BeginInvoke 和 EndInvoke 方法異步啟動(dòng)調(diào)用和檢索數(shù)據(jù)的詳細(xì)信息,請參閱“Asynchronous Programming Overview”(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpovrasynchronousprogrammingoverview.asp)。
注有關(guān)設(shè)計(jì)和構(gòu)建偶爾連接到網(wǎng)絡(luò)的智能客戶端應(yīng)用程序的詳細(xì)信息,請參閱第 3 章“建立連接”和第 4 章“偶爾連接的智能客戶端”。
線程處理原則
在應(yīng)用程序內(nèi)使用多個(gè)線程可能是一種提高其響應(yīng)性和性能的好方法。特別地,您應(yīng)該考慮使用線程來執(zhí)行可以在后臺(tái)安全地完成且不需要用戶交互的處理。通過在后臺(tái)執(zhí)行此類工作,可以使用戶能夠繼續(xù)使用應(yīng)用程序,并且使應(yīng)用程序的主用戶界面線程能夠維持應(yīng)用程序的響應(yīng)性。
適合于在單獨(dú)的線程上完成的處理包括:
? |
應(yīng)用程序初始化。請?jiān)诤笈_(tái)線程上執(zhí)行漫長的初始化,以便用戶能夠盡快地與您的應(yīng)用程序交互,尤其是在應(yīng)用程序功能的重要或主要部分并不依賴于該初始化完成時(shí)。 |
? |
遠(yuǎn)程服務(wù)調(diào)用。請?jiān)趩为?dú)的后臺(tái)線程上通過網(wǎng)絡(luò)進(jìn)行所有遠(yuǎn)程調(diào)用。很難(如果不是無法)保證位于網(wǎng)絡(luò)上的服務(wù)的響應(yīng)時(shí)間。在單獨(dú)的線程上執(zhí)行這些調(diào)用可以減少發(fā)生網(wǎng)絡(luò)中斷或延遲的風(fēng)險(xiǎn),從而避免對應(yīng)用程序性能造成不利影響。 |
? |
IO 綁定處理。應(yīng)該在單獨(dú)的線程上完成諸如在磁盤上搜索和排序數(shù)據(jù)之類的處理。通常,這種工作要受到磁盤 I/O 子系統(tǒng)而不是處理器可用性的限制,因此當(dāng)該工作在后臺(tái)執(zhí)行時(shí),您的應(yīng)用程序可以有效地維持其響應(yīng)性。 |
盡管使用多個(gè)線程的性能好處可能很顯著,但需要注意,線程使用它們自己的資源,并且使用太多的線程可能給處理器(它需要管理線程之間的上下文切換)造成負(fù)擔(dān)。要避免這一點(diǎn),請考慮使用線程池,而不是創(chuàng)建和管理您自己的線程。線程池將為您有效地管理線程,重新使用現(xiàn)有的線程對象,并且盡可能地減小與線程創(chuàng)建和處置相關(guān)聯(lián)的系統(tǒng)開銷。
如果用戶體驗(yàn)受到后臺(tái)線程所執(zhí)行的工作的影響,則您應(yīng)該總是讓用戶了解工作的進(jìn)度。以這種方式提供反饋可以增強(qiáng)用戶對您的應(yīng)用程序的性能的感覺,并且防止他或她假設(shè)沒有任何事情發(fā)生。請努力確保用戶可以隨時(shí)取消漫長的操作。
您還應(yīng)該考慮使用 Application 對象的 Idle 事件來執(zhí)行簡單的操作。Idle 事件提供了使用單獨(dú)的線程來進(jìn)行后臺(tái)處理的簡單替代方案。當(dāng)應(yīng)用程序不再有其他用戶界面消息需要處理并且將要進(jìn)入空閑狀態(tài)時(shí),該事件將激發(fā)。您可以通過該事件執(zhí)行簡單的操作,并且利用用戶不活動(dòng)的情況。例如:
[C#]
public Form1()
{
InitializeComponent();
Application.Idle += new EventHandler( OnApplicationIdle );
}
private void OnApplicationIdle( object sender, EventArgs e )
{
}
[Visual Basic .NET]
Public Class Form1
Inherits System.Windows.Forms.Form
Public Sub New()
MyBase.New()
InitializeComponent()
AddHandler Application.Idle, AddressOf OnApplicationIdle
End Sub
Private Sub OnApplicationIdle(ByVal sender As System.Object, ByVal e As System.EventArgs)
End Sub
End Class
注有關(guān)在智能客戶端中使用多個(gè)線程的詳細(xì)信息,請參閱第 6 章“使用多個(gè)線程”。
事務(wù)原則
事務(wù)可以提供重要的支持,以確保不會(huì)違反業(yè)務(wù)規(guī)則并維護(hù)數(shù)據(jù)一致性。事務(wù)可以確保一組相關(guān)任務(wù)作為一個(gè)單元成功或失敗。您可以使用事務(wù)來維護(hù)本地?cái)?shù)據(jù)庫和其他資源(包括消息隊(duì)列的隊(duì)列)之間的一致性。
對于需要在網(wǎng)絡(luò)連接不可用時(shí)使用脫機(jī)緩存數(shù)據(jù)的智能客戶端應(yīng)用程序,您應(yīng)該將事務(wù)性數(shù)據(jù)排隊(duì),并且在網(wǎng)絡(luò)連接可用時(shí)將其與服務(wù)器進(jìn)行同步。
您應(yīng)該避免使用涉及到位于網(wǎng)絡(luò)上的資源的分布式事務(wù),因?yàn)檫@些情況可能導(dǎo)致與不斷變化的網(wǎng)絡(luò)和資源響應(yīng)時(shí)間有關(guān)的性能問題。如果您的應(yīng)用程序需要在事務(wù)中涉及到位于網(wǎng)絡(luò)上的資源,則應(yīng)該考慮使用補(bǔ)償事務(wù),以便使您的應(yīng)用程序能夠在本地事務(wù)失敗時(shí)取消以前的請求。盡管補(bǔ)償事務(wù)在某些情況下可能不適用,但它們使您的應(yīng)用程序能夠按照松耦合方式在事務(wù)的上下文內(nèi)與網(wǎng)絡(luò)資源交互,從而減少了不在本地計(jì)算機(jī)控制之下的資源對應(yīng)用程序的性能造成不利影響的可能性。
注有關(guān)在智能客戶端中使用事務(wù)的詳細(xì)信息,請參閱第 3 章“建立連接”。
優(yōu)化應(yīng)用程序啟動(dòng)時(shí)間
快速的應(yīng)用程序啟動(dòng)時(shí)間幾乎可以使用戶立即開始與應(yīng)用程序交互,從而使用戶立刻對應(yīng)用程序的性能和可用性產(chǎn)生好感。
當(dāng)應(yīng)用程序啟動(dòng)時(shí),首先加載 CLR,再加載應(yīng)用程序的主程序集,隨后加載為解析從應(yīng)用程序的主窗體中引用的對象的類型所需要的所有程序集。CLR 在該階段不會(huì) 加載所有相關(guān)程序集;它僅加載包含主窗體類上的成員變量的類型定義的程序集。在加載了這些程序集之后,實(shí)時(shí) (JIT) 編譯器將在方法運(yùn)行時(shí)編譯方法的代碼(從 Main 方法開始)。同樣,JIT 編譯器不會(huì) 編譯您的程序集中的所有代碼。相反,將根據(jù)需要逐個(gè)方法地編譯代碼。
要盡可能減少應(yīng)用程序的啟動(dòng)時(shí)間,您應(yīng)該遵循下列原則:
? |
盡可能減少應(yīng)用程序主窗體類中的成員變量。這將在 CLR 加載主窗體類時(shí)盡可能減少必須解析的類型數(shù)量。 |
? |
盡量不要立即使用大型基類程序集(XML 庫或 ADO.NET 庫)中的類型。這些程序集的加載很費(fèi)時(shí)間。使用應(yīng)用程序配置類和跟蹤開關(guān)功能時(shí)將引入 XML 庫。如果要優(yōu)先考慮應(yīng)用程序啟動(dòng)時(shí)間,請避免這一點(diǎn)。 |
? |
盡可能使用惰性加載。僅在需要時(shí)獲取數(shù)據(jù),而不是提前加載和凍結(jié) UI。 |
? |
將應(yīng)用程序設(shè)計(jì)為使用較少的程序集。帶有大量程序集的應(yīng)用程序會(huì)招致性能開銷增加。這些開銷來自加載元數(shù)據(jù)、訪問 CLR 中的預(yù)編譯映像中的各種內(nèi)存頁以加載程序集(如果它是用本機(jī)映像生成器工具 Ngen.exe 預(yù)編譯的)、JIT 編譯時(shí)間、安全檢查等等。您應(yīng)該考慮基于程序集的使用模式來合并程序集,以便降低相關(guān)聯(lián)的性能開銷。 |
? |
避免設(shè)計(jì)將多個(gè)組件的功能組合到一個(gè)組件中的單一類。將設(shè)計(jì)分解到多個(gè)只須在實(shí)際調(diào)用時(shí)進(jìn)行編譯的較小類。 |
? |
將應(yīng)用程序設(shè)計(jì)為在初始化期間對網(wǎng)絡(luò)服務(wù)進(jìn)行并行調(diào)用。通過在初始化期間調(diào)用可以并行運(yùn)行的網(wǎng)絡(luò)服務(wù),可以利用服務(wù)代理提供的異步功能。這有助于釋放當(dāng)前執(zhí)行的線程并且并發(fā)地調(diào)用服務(wù)以完成任務(wù)。 |
? |
使用 NGEN.exe 編譯和試驗(yàn) NGen 和非 NGen 程序集,并且確定哪個(gè)程序集保存了最大數(shù)量的工作集頁面。NGEN.exe(它隨附在 .NET Framework 中)用于預(yù)編譯程序集以創(chuàng)建本機(jī)映像,該映像隨后被存儲(chǔ)在全局程序集緩存的特殊部分,以便應(yīng)用程序下次需要它時(shí)使用。通過創(chuàng)建程序集的本機(jī)映像,可以使程序集更快地加載和執(zhí)行,因?yàn)?CLR 不需要?jiǎng)討B(tài)生成程序集中包含的代碼和數(shù)據(jù)結(jié)構(gòu)。有關(guān)詳細(xì)信息,請參閱 Improving .NET Application Performance and Scalability 的第 5 章“Improving Managed Code Performance”中的“Working Set Considerations”和“NGen.exe Explained”部分,網(wǎng)址為:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt05.asp。
注如果您使用 NGEN 預(yù)編譯程序集,則會(huì)立即加載它的所有依賴程序集。 |
管理可用資源
公共語言運(yùn)行庫 (CLR) 使用垃圾回收器來管理對象生存期和內(nèi)存使用。這意味著無法再訪問的對象將被垃圾回收器自動(dòng)回收,并且自動(dòng)回收內(nèi)存。由于多種原因無法再訪問對象。例如,可能沒有對該對象的任何引用,或者對該對象的所有引用可能來自其他可作為當(dāng)前回收周期的一部分進(jìn)行回收的對象。盡管自動(dòng)垃圾回收使您的代碼不必負(fù)責(zé)管理對象刪除,但這意味著您的代碼不再對對象的確切刪除時(shí)間具有顯式控制。
請考慮下列原則,以確保您能夠有效地管理可用資源:
? |
確保在被調(diào)用方對象提供 Dispose 方法時(shí)該方法得到調(diào)用。如果您的代碼調(diào)用了支持 Dispose 方法的對象,則您應(yīng)該確保在使用完該對象之后立即調(diào)用此方法。調(diào)用 Dispose 方法可以確保搶先釋放非托管資源,而不是等到發(fā)生垃圾回收。除了提供 Dispose 方法以外,某些對象還提供其他管理資源的方法,例如,Close 方法。在這些情況下,您應(yīng)該參考文檔資料以了解如何使用其他方法。例如,對于 SqlConnection 對象而言,調(diào)用 Close 或 Dispose 都足可以搶先將數(shù)據(jù)庫連接釋放回連接池中。一種可以確保您在對象使用完畢之后立即調(diào)用 Dispose 的方法是使用 Visual C# .NET 中的 using 語句或 Visual Basic .NET 中的 Try/Finally 塊。
下面的代碼片段演示了 Dispose 的用法。
C# 中的 using 語句示例: using( StreamReader myFile = new StreamReader("C:\\ReadMe.Txt")){
string contents = myFile.ReadToEnd();
//... use the contents of the file
} // dispose is called and the StreamReader's resources released
Visual Basic .NET 中的 Try/Finally 塊示例: Dim myFile As StreamReader
myFile = New StreamReader("C:\\ReadMe.Txt")
Try
String contents = myFile.ReadToEnd()
'... use the contents of the file
Finally
myFile.Close()
End Try
注在 C# 和 C++ 中,F(xiàn)inalize 方法是作為析構(gòu)函數(shù)實(shí)現(xiàn)的。在 Visual Basic .NET 中,Finalize 方法是作為 Object 基類上的 Finalize 子例程的重寫實(shí)現(xiàn)的。 |
? |
如果您在客戶端調(diào)用過程中占據(jù)非托管資源,則請?zhí)峁?/B> Finalize 和 Dispose 方法。如果您在公共或受保護(hù)的方法調(diào)用中創(chuàng)建訪問非托管資源的對象,則應(yīng)用程序需要控制非托管資源的生存期。在圖 8.1 中,第一種情況是對非托管資源的調(diào)用,在此將打開、獲取和關(guān)閉資源。在此情況下,您的對象無須提供 Finalize 和 Dispose 方法。在第二種情況下,在方法調(diào)用過程中占據(jù)非托管資源;因此,您的對象應(yīng)該提供 Finalize 和 Dispose 方法,以便客戶端在使用完該對象后可以立即顯式釋放資源。
圖 8.1:Dispose 和 Finalize 方法調(diào)用的用法
|
垃圾回收通常有利于提高總體性能,因?yàn)樗鼘⑺俣鹊闹匾灾糜趦?nèi)存利用率之上。只有當(dāng)內(nèi)存資源不足時(shí),才需要?jiǎng)h除對象;否則,將使用所有可用的應(yīng)用程序資源以使您的應(yīng)用程序受益。但是,如果您的對象保持對非托管資源(例如,窗口句柄、文件、GDI 對象和網(wǎng)絡(luò)連接)的引用,則程序員通過在這些資源不再使用時(shí)顯式釋放它們可以獲得更好的性能。如果您要在客戶端方法調(diào)用過程中占據(jù)非托管資源,則對象應(yīng)該允許調(diào)用方使用 IDisposable 接口(它提供 Dispose 方法)顯式管理資源。通過實(shí)現(xiàn) IDisposable,對象將通知它可被要求明確進(jìn)行清理,而不是等待垃圾回收。實(shí)現(xiàn) IDisposable 的對象的調(diào)用方在使用完該對象后將簡單地調(diào)用 Dispose 方法,以便它可以根據(jù)需要釋放資源。
有關(guān)如何在某個(gè)對象上實(shí)現(xiàn) IDisposable 的詳細(xì)信息,請參閱 Improving .NET Application Performance and Scalability 中的第 5 章“Improving Managed Code Performance”,網(wǎng)址為:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt05.asp。
注如果您的可處置對象派生自另一個(gè)也實(shí)現(xiàn)了 IDisposable 接口的對象,則您應(yīng)該調(diào)用基類的 Dispose 方法以使其可以清理它的資源。您還應(yīng)該調(diào)用實(shí)現(xiàn)了 IDisposable 接口的對象所擁有的所有對象上的 Dispose。
Finalize 方法也使您的對象可以在刪除時(shí)顯式釋放其引用的任何資源。由于垃圾回收器所具有的非確定性,在某些情況下,Finalize 方法可能長時(shí)間不會(huì)被調(diào)用。實(shí)際上,如果您的應(yīng)用程序在垃圾回收器刪除對象之前終止,則該方法可能永遠(yuǎn)不會(huì)被調(diào)用。然而,需要使用 Finalize 方法作為一種后備策略,以防調(diào)用方?jīng)]有顯式調(diào)用 Dispose 方法(Dispose 和 Finalize 方法共享相同的資源清理代碼)。通過這種方式,可能在某個(gè)時(shí)刻釋放資源,即使這發(fā)生在最佳時(shí)刻之后。
注要確保 Dispose 和 Finalize 中的清理代碼不會(huì)被調(diào)用兩次,您應(yīng)該調(diào)用 GC.SuppressFinalize 以通知垃圾回收器不要調(diào)用 Finalize 方法。
垃圾回收器實(shí)現(xiàn)了 Collect 方法,該方法強(qiáng)制垃圾回收器刪除所有對象掛起刪除。不應(yīng)該從應(yīng)用程序內(nèi)調(diào)用該方法,因?yàn)榛厥罩芷谠诟邇?yōu)先級(jí)線程上運(yùn)行。回收周期可能凍結(jié)所有 UI 線程,從而使得用戶界面停止響應(yīng)。
有關(guān)詳細(xì)信息,請參閱 Improving .NET Application Performance and Scalability 中的“Garbage Collection Guidelines”、“Finalize and Dispose Guidelines”、“Dispose Pattern”和“Finalize and Dispose Guidelines”,網(wǎng)址為:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt05.asp。
優(yōu)化 Windows 窗體性能
Windows 窗體為智能客戶端應(yīng)用程序提供了內(nèi)容豐富的用戶界面,并且您可以使用許多種技術(shù)來幫助確保 Windows 窗體提供最佳性能。在討論特定技術(shù)之前,對一些可以顯著提高 Windows 窗體性能的高級(jí)原則進(jìn)行回顧是有用的。
? |
小心創(chuàng)建句柄。Windows 窗體將句柄創(chuàng)建虛擬化(即,它動(dòng)態(tài)創(chuàng)建和重新創(chuàng)建窗口句柄對象)。創(chuàng)建句柄對象的系統(tǒng)開銷可能非常大;因此,請避免進(jìn)行不必要的邊框樣式更改或者更改 MDI 父對象。 |
? |
避免創(chuàng)建帶有太多子控件的應(yīng)用程序。Microsoft? Windows? 操作系統(tǒng)限制每個(gè)進(jìn)程最多有 10,000 個(gè)控件,但您應(yīng)該避免在窗體上使用成百上千個(gè)控件,因?yàn)槊總€(gè)控件都要消耗內(nèi)存資源。 |
本節(jié)的其余部分討論您可以用來優(yōu)化應(yīng)用程序用戶界面性能的更為具體的技術(shù)。
使用 BeginUpdate 和 EndUpdate
許多 Windows 窗體控件(例如,ListView 和 TreeView 控件)實(shí)現(xiàn)了 BeginUpdate 和 EndUpdate 方法,它們在操縱基礎(chǔ)數(shù)據(jù)或控件屬性時(shí)取消了控件的重新繪制。通過使用 BeginUpdate 和 EndUpdate 方法,您可以對控件進(jìn)行重大更改,并且避免在應(yīng)用這些更改時(shí)讓控件經(jīng)常重新繪制自身。此類重新繪制會(huì)導(dǎo)致性能顯著降低,并且用戶界面閃爍且不反應(yīng)。
例如,如果您的應(yīng)用程序具有一個(gè)要求添加大量節(jié)點(diǎn)項(xiàng)的樹控件,則您應(yīng)該調(diào)用 BeginUpdate,添加所有必需的節(jié)點(diǎn)項(xiàng),然后調(diào)用 EndUpdate。下面的代碼示例顯示了一個(gè)樹控件,該控件用于顯示許多個(gè)客戶的層次結(jié)構(gòu)表示形式及其定單信息。
[C#]
// Suppress repainting the TreeView until all the objects have been created.
TreeView1.BeginUpdate();
// Clear the TreeView.
TreeView1.Nodes.Clear();
// Add a root TreeNode for each Customer object in the ArrayList.
foreach( Customer customer2 in customerArray )
{
TreeView1.Nodes.Add( new TreeNode( customer2.CustomerName ) );
// Add a child TreeNode for each Order object in the current Customer.
foreach( Order order1 in customer2.CustomerOrders )
{
TreeView1.Nodes[ customerArray.IndexOf(customer2) ].Nodes.Add(
new TreeNode( customer2.CustomerName + "." + order1.OrderID ) );
}
}
// Begin repainting the TreeView.
TreeView1.EndUpdate();
[Visual Basic .NET]
' Suppress repainting the TreeView until all the objects have been created.
TreeView1.BeginUpdate()
' Clear the TreeView
TreeView1.Nodes.Clear()
' Add a root TreeNode for each Customer object in the ArrayList
For Each customer2 As Customer In customerArray
TreeView1.Nodes.Add(New TreeNode(customer2.CustomerName))
' Add a child TreeNode for each Order object in the current Customer.
For Each order1 As Order In customer2.CustomerOrders
TreeView1.Nodes(Array.IndexOf(customerArray, customer2)).Nodes.Add( _
New TreeNode(customer2.CustomerName & "." & order1.OrderID))
Next
Next
' Begin repainting the TreeView.
TreeView1.EndUpdate()
即使在您不希望向控件添加許多對象時(shí),您也應(yīng)該使用 BeginUpdate 和 EndUpdate 方法。在大多數(shù)情況下,您在運(yùn)行之前將不知道要添加的項(xiàng)的確切個(gè)數(shù)。因此,為了妥善處理大量數(shù)據(jù)以及應(yīng)付將來的要求,您應(yīng)該總是調(diào)用 BeginUpdate 和 EndUpdate 方法。
注調(diào)用 Windows 窗體控件使用的許多 Collection 類的 AddRange 方法時(shí),將自動(dòng)為您調(diào)用 BeginUpdate 和 EndUpdate 方法。
使用 SuspendLayout 和 ResumeLayout
許多 Windows 窗體控件(例如,ListView 和 TreeView 控件)都實(shí)現(xiàn)了 SuspendLayout 和 ResumeLayout 方法,它們能夠防止控件在添加子控件時(shí)創(chuàng)建多個(gè)布局事件。
如果您的控件以編程方式添加和刪除子控件或者執(zhí)行動(dòng)態(tài)布局,則您應(yīng)該調(diào)用 SuspendLayout 和 ResumeLayout 方法。通過 SuspendLayout 方法,可以在控件上執(zhí)行多個(gè)操作,而不必為每個(gè)更改執(zhí)行布局。例如,如果您調(diào)整控件的大小并移動(dòng)控件,則每個(gè)操作都將引發(fā)單獨(dú)的布局事件。
這些方法按照與 BeginUpdate 和 EndUpdate 方法類似的方式操作,并且在性能和用戶界面穩(wěn)定性方面提供相同的好處。
下面的示例以編程方式向父窗體中添加按鈕:
[C#]
private void AddButtons()
{
// Suspend the form layout and add two buttons.
this.SuspendLayout();
Button buttonOK = new Button();
buttonOK.Location = new Point(10, 10);
buttonOK.Size = new Size(75, 25);
buttonOK.Text = "OK";
Button buttonCancel = new Button();
buttonCancel.Location = new Point(90, 10);
buttonCancel.Size = new Size(75, 25);
buttonCancel.Text = "Cancel";
this.Controls.AddRange(new Control[]{buttonOK, buttonCancel});
this.ResumeLayout();
}
[Visual Basic .NET]
Private Sub AddButtons()
' Suspend the form layout and add two buttons
Me.SuspendLayout()
Dim buttonOK As New Button
buttonOK.Location = New Point(10, 10)
buttonOK.Size = New Size(75, 25)
buttonOK.Text = "OK"
Dim buttonCancel As New Button
buttonCancel.Location = New Point(90, 10)
buttonCancel.Size = New Size(75, 25)
buttonCancel.Text = "Cancel"
Me.Controls.AddRange(New Control() { buttonOK, buttonCancel } )
Me.ResumeLayout()
End Sub
每當(dāng)您添加或刪除控件、執(zhí)行子控件的自動(dòng)布局或者設(shè)置任何影響控件布局的屬性(例如,大小、位置、定位點(diǎn)或停靠屬性)時(shí),您都應(yīng)該使用 SuspendLayout 和 ResumeLayout 方法。
處理圖像
如果您的應(yīng)用程序顯示大量圖像文件(例如,.jpg 和 .gif 文件),則您可以通過以位圖格式預(yù)先呈現(xiàn)圖像來顯著改善顯示性能。
要使用該技術(shù),請首先從文件中加載圖像,然后使用 PARGB 格式將其呈現(xiàn)為位圖。下面的代碼示例從磁盤中加載文件,然后使用該類將圖像呈現(xiàn)為預(yù)乘的、Alpha 混合 RGB 格式。例如:
[C#]
if ( image != null && image is Bitmap )
{
Bitmap bm = (Bitmap)image;
Bitmap newImage = new Bitmap( bm.Width, bm.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb );
using ( Graphics g = Graphics.FromImage( newImage ) )
{
g.DrawImage( bm, new Rectangle( 0,0, bm.Width, bm.Height ) );
}
image = newImage;
}
[Visual Basic .NET]
If Not(image Is Nothing) AndAlso (TypeOf image Is Bitmap) Then
Dim bm As Bitmap = CType(image, Bitmap)
Dim newImage As New Bitmap(bm.Width, bm.Height, _
System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
Using g As Graphics = Graphics.FromImage(newImage)
g.DrawImage(bm, New Rectangle(0, 0, bm.Width, bm.Height))
End Using
image = newImage
End If
使用分頁和惰性加載
在大多數(shù)情況下,您應(yīng)該僅在需要時(shí)檢索或顯示數(shù)據(jù)。如果您的應(yīng)用程序需要檢索和顯示大量信息,則您應(yīng)該考慮將數(shù)據(jù)分解到多個(gè)頁面中,并且一次顯示一頁數(shù)據(jù)。這可以使用戶界面具有更高的性能,因?yàn)樗鼰o須顯示大量數(shù)據(jù)。此外,這可以提高應(yīng)用程序的可用性,因?yàn)橛脩舨粫?huì)同時(shí)面對大量數(shù)據(jù),并且可以更加容易地導(dǎo)航以查找他或她需要的確切數(shù)據(jù)。
例如,如果您的應(yīng)用程序顯示來自大型產(chǎn)品目錄的產(chǎn)品數(shù)據(jù),則您可以按照字母順序顯示這些項(xiàng),并且將所有以“A”開頭的產(chǎn)品顯示在一個(gè)頁面上,將所有以“B”開頭的產(chǎn)品顯示在下一個(gè)頁面上。然后,您可以讓用戶直接導(dǎo)航到適當(dāng)?shù)捻撁妫员闼蛩裏o須瀏覽所有頁面就可以獲得他或她需要的數(shù)據(jù)。
以這種方式將數(shù)據(jù)分頁還使您可以根據(jù)需要獲取后臺(tái)的數(shù)據(jù)。例如,您可能只需要獲取第一頁信息以便顯示并且讓用戶與其進(jìn)行交互。然后,您可以獲取后臺(tái)中的、已經(jīng)準(zhǔn)備好供用戶使用的下一頁數(shù)據(jù)。該技術(shù)在與數(shù)據(jù)緩存技術(shù)結(jié)合使用時(shí)可能特別有效。
您還可以通過使用惰性加載技術(shù)來提高智能客戶端應(yīng)用程序的性能。您無須立即加載可能在將來某個(gè)時(shí)刻需要的數(shù)據(jù)或資源,而是可以根據(jù)需要加載它們。您可以在構(gòu)建大型列表或樹結(jié)構(gòu)時(shí)使用惰性加載來提高用戶界面的性能。在此情況下,您可以在用戶需要看到數(shù)據(jù)時(shí)(例如,在用戶展開樹節(jié)點(diǎn)時(shí))加載它。
優(yōu)化顯示速度
根據(jù)您用于顯示用戶界面控件和應(yīng)用程序窗體的技術(shù),您可以用多種不同的方式來優(yōu)化應(yīng)用程序的顯示速度。
當(dāng)您的應(yīng)用程序啟動(dòng)時(shí),您應(yīng)該考慮盡可能地顯示簡單的用戶界面。這將減少啟動(dòng)時(shí)間,并且向用戶呈現(xiàn)整潔且易于使用的用戶界面。而且,您應(yīng)該努力避免引用類以及在啟動(dòng)時(shí)加載任何不會(huì)立刻需要的數(shù)據(jù)。這將減少應(yīng)用程序和 .NET Framework 初始化時(shí)間,并且提高應(yīng)用程序的顯示速度。
當(dāng)您需要顯示對話框或窗體時(shí),您應(yīng)該在它們做好顯示準(zhǔn)備之前使其保持隱藏狀態(tài),以便減少需要的繪制工作量。這將有助于確保窗體僅在初始化之后顯示。
如果您的應(yīng)用程序具有的控件含有覆蓋整個(gè)客戶端表面區(qū)域的子控件,則您應(yīng)該考慮將控件背景樣式設(shè)置為不透明。這可以避免在發(fā)生每個(gè)繪制事件時(shí)重繪控件的背景。您可以通過使用 SetStyle 方法來設(shè)置控件的樣式。使用 ControlsStyles.Opaque 枚舉可以指定不透明控件樣式。
您應(yīng)該避免任何不必要的控件重新繪制操作。一種方法是在設(shè)置控件的屬性時(shí)隱藏控件。在 OnPaint 事件中具有復(fù)雜繪圖代碼的應(yīng)用程序能夠只重繪窗體的無效區(qū)域,而不是繪制整個(gè)窗體。OnPaint 事件的 PaintEventArgs 參數(shù)包含一個(gè) ClipRect 結(jié)構(gòu),它指示窗口的哪個(gè)部分無效。這可以減少用戶等待查看完整顯示的時(shí)間。
使用標(biāo)準(zhǔn)的繪圖優(yōu)化,例如,剪輯、雙緩沖和 ClipRectangle。這還將通過防止對不可見或要求重繪的顯示部分執(zhí)行不必要的繪制操作,從而有助于改善智能客戶端應(yīng)用程序的顯示性能。有關(guān)增強(qiáng)繪圖性能的詳細(xì)信息,請參閱 Painting techniques using Windows Forms for the Microsoft .NET Framework,網(wǎng)址為:http://windowsforms.net/articles/windowsformspainting.aspx。
如果您的顯示包含動(dòng)畫或者經(jīng)常更改某個(gè)顯示元素,則您應(yīng)該使用雙緩沖或多緩沖,在繪制當(dāng)前圖像的過程中準(zhǔn)備下一個(gè)圖像。System.Windows.Forms 命名空間中的 ControlStyles 枚舉適用于許多控件,并且 DoubleBuffer 成員可以幫助防止閃爍。啟用 DoubleBuffer 樣式將使您的控件繪制在離屏緩沖中完成,然后同時(shí)繪制到屏幕上。盡管這有助于防止閃爍,但它的確為分配的緩沖區(qū)使用了更多內(nèi)存。
性能調(diào)整和診斷
在設(shè)計(jì)和實(shí)現(xiàn)階段處理性能問題是實(shí)現(xiàn)應(yīng)用程序性能目標(biāo)的最劃算的方法。但是,您只有在開發(fā)階段經(jīng)常且盡早測試應(yīng)用程序的性能,才能真正有效地優(yōu)化應(yīng)用程序的性能。
盡管針對性能進(jìn)行設(shè)計(jì)和測試都很重要,但在這些早期階段優(yōu)化每個(gè)組件和所有代碼不是有效的資源用法,因此應(yīng)該予以避免。所以,應(yīng)用程序可能存在您在設(shè)計(jì)階段未預(yù)料到的性能問題。例如,您可能遇到由于兩個(gè)系統(tǒng)或組件之間的無法預(yù)料的交互而產(chǎn)生的性能問題,或者您可能使用原來存在的、未按希望的方式執(zhí)行的代碼。在此情況下,您需要追究性能問題的根源,以便您可以適當(dāng)?shù)亟鉀Q該問題。
本節(jié)討論一些將幫助您診斷性能問題以及調(diào)整應(yīng)用程序以獲得最佳性能的工具和技術(shù)。
制定性能目標(biāo)
當(dāng)您設(shè)計(jì)和規(guī)劃智能客戶端應(yīng)用程序時(shí),您應(yīng)該仔細(xì)考慮性能方面的要求,并且定義合適的性能目標(biāo)。在定義這些目標(biāo)時(shí),請考慮您將如何度量應(yīng)用程序的實(shí)際性能。您的性能度量標(biāo)準(zhǔn)應(yīng)該明確體現(xiàn)應(yīng)用程序的重要性能特征。請努力避免無法準(zhǔn)確度量的模糊或不完整的目標(biāo),例如,“應(yīng)用程序必須快速運(yùn)行”或“應(yīng)用程序必須快速加載”。您需要了解應(yīng)用程序的性能和可伸縮性目標(biāo),以便您可以設(shè)法滿足這些目標(biāo)并且圍繞它們來規(guī)劃您的測試。請確保您的目標(biāo)是可度量的和可驗(yàn)證的。
定義良好的性能度量標(biāo)準(zhǔn)使您可以準(zhǔn)確跟蹤應(yīng)用程序的性能,以便您可以確定應(yīng)用程序是否能夠滿足它的性能目標(biāo)。這些度量標(biāo)準(zhǔn)應(yīng)該包括在應(yīng)用程序測試計(jì)劃中,以便可以在應(yīng)用程序的測試階段度量它們。
本節(jié)重點(diǎn)討論與智能客戶端應(yīng)用程序相關(guān)的特定性能目標(biāo)的定義。如果您還要設(shè)計(jì)和生成客戶端應(yīng)用程序?qū)⑾牡木W(wǎng)絡(luò)服務(wù),則您還需要為這些服務(wù)定義適當(dāng)?shù)男阅苣繕?biāo)。在此情況下,您應(yīng)該確保考慮整個(gè)系統(tǒng)的性能要求,以及應(yīng)用程序各個(gè)部分的性能與其他部分以及整個(gè)系統(tǒng)之間存在怎樣的關(guān)系。
考慮用戶的觀點(diǎn)
當(dāng)您為智能客戶端應(yīng)用程序確定合適的性能目標(biāo)時(shí),您應(yīng)該仔細(xì)考慮用戶的觀點(diǎn)。對于智能客戶端應(yīng)用程序而言,性能與可用性和用戶感受有關(guān)。例如,只要用戶能夠繼續(xù)工作并且獲得有關(guān)操作進(jìn)度的足夠反饋,用戶就可以接受漫長的操作。
在確定要求時(shí),將應(yīng)用程序的功能分解為多個(gè)使用情景或使用案例通常是有用的。您應(yīng)該識(shí)別對于實(shí)現(xiàn)特定性能目標(biāo)而言關(guān)鍵且必需的使用案例和情景。應(yīng)該將許多使用案例所共有且經(jīng)常執(zhí)行的任務(wù)設(shè)計(jì)得具有較高性能。同樣,如果任務(wù)要求用戶全神貫注并且不允許用戶從其切換以執(zhí)行其他任務(wù),則需要提供優(yōu)化的且有效的用戶體驗(yàn)。如果任務(wù)不太經(jīng)常使用且不會(huì)阻止用戶執(zhí)行其他任務(wù),則可能無須進(jìn)行大量調(diào)整。
對于您識(shí)別的每個(gè)性能敏感型任務(wù),您都應(yīng)該精確地定義用戶的操作以及應(yīng)用程序的響應(yīng)方式。您還應(yīng)該確定每個(gè)任務(wù)使用的網(wǎng)絡(luò)和客戶端資源或組件。該信息將影響性能目標(biāo),并且將驅(qū)動(dòng)對性能進(jìn)行度量的測試。
可用性研究提供了非常有價(jià)值的信息源,并且可能大大影響性能目標(biāo)的定義。正式的可用性研究在確定用戶如何執(zhí)行他們的工作、哪些使用情景是共有的以及哪些不是共有的、用戶經(jīng)常執(zhí)行哪些任務(wù)以及從性能觀點(diǎn)看來應(yīng)用程序的哪些特征是重要的等方面可能非常有用。如果您要生成新的應(yīng)用程序,您應(yīng)該考慮提供應(yīng)用程序的原型或模型,以便可以執(zhí)行基本的可用性測試。
考慮應(yīng)用程序操作環(huán)境
對應(yīng)用程序的操作環(huán)境進(jìn)行評(píng)估是很重要的,因?yàn)檫@可能對應(yīng)用程序施加必須在您制定的性能目標(biāo)中予以反映的約束。
位于網(wǎng)絡(luò)上的服務(wù)可能對您的應(yīng)用程序施加性能約束。例如,您可能需要與您無法控制的 Web 服務(wù)進(jìn)行交互。在這種情況下,需要確定該服務(wù)的性能,并且確定這是否將對客戶端應(yīng)用程序的性能產(chǎn)生影響。
您還應(yīng)該確定任何相關(guān)服務(wù)和組件的性能如何隨著時(shí)間的變化而變化。某些系統(tǒng)會(huì)經(jīng)受相當(dāng)穩(wěn)定的使用,而其他系統(tǒng)則會(huì)在一天或一周的特定時(shí)間經(jīng)受變動(dòng)極大的使用。這些區(qū)別可能在關(guān)鍵時(shí)間對應(yīng)用程序的性能造成不利影響。例如,提供應(yīng)用程序部署和更新服務(wù)的服務(wù)可能會(huì)在星期一早上 9 點(diǎn)緩慢響應(yīng),因?yàn)樗杏脩舳荚诖藭r(shí)升級(jí)到應(yīng)用程序的最新版本。
另外,還需要準(zhǔn)確地對所有相關(guān)系統(tǒng)和組件的性能進(jìn)行建模,以便可以在嚴(yán)格模擬應(yīng)用程序的實(shí)際部署環(huán)境的環(huán)境中測試您的應(yīng)用程序。對于每個(gè)系統(tǒng),您都應(yīng)該確定性能概況以及最低、平均和最高性能特征。然后,您可以在定義應(yīng)用程序的性能要求時(shí)根據(jù)需要使用該數(shù)據(jù)。
您還應(yīng)該仔細(xì)考慮用于運(yùn)行應(yīng)用程序的硬件。您將需要確定在處理器、內(nèi)存、圖形功能等方面的目標(biāo)硬件配置,或者至少確定一個(gè)如果得不到滿足則無法保證性能的最低配置。
通常,應(yīng)用程序的業(yè)務(wù)操作環(huán)境將規(guī)定一些更為苛刻的性能要求。例如,執(zhí)行實(shí)時(shí)股票交易的應(yīng)用程序?qū)⑿枰獔?zhí)行這些交易并及時(shí)顯示所有相關(guān)數(shù)據(jù)。
性能調(diào)整過程
對應(yīng)用程序進(jìn)行性能調(diào)整是一個(gè)迭代過程。該過程由一些重復(fù)執(zhí)行直至應(yīng)用程序滿足其性能目標(biāo)的階段組成。(請參見圖 8.2。)
圖 8.2:性能調(diào)整過程
正如圖 8.2 所闡明的,性能調(diào)整要求您完成下列過程:
? |
建立基準(zhǔn)。在您開始針對性能調(diào)整應(yīng)用程序時(shí),您必須具有與性能目標(biāo)、目標(biāo)和度量標(biāo)準(zhǔn)有關(guān)的定義良好的基準(zhǔn)。這可能包括應(yīng)用程序工作集大小、加載數(shù)據(jù)(例如,目錄)的時(shí)間、事務(wù)持續(xù)時(shí)間等等。 |
? |
收集數(shù)據(jù)。您將需要通過針對您已經(jīng)定義的性能目標(biāo)度量應(yīng)用程序的性能,來對應(yīng)用程序性能進(jìn)行評(píng)價(jià)。性能目標(biāo)應(yīng)該體現(xiàn)特定的且可度量的度量標(biāo)準(zhǔn),以使您可以在任何時(shí)刻量化應(yīng)用程序的性能。要使您可以收集性能數(shù)據(jù),您可能必須對應(yīng)用程序進(jìn)行規(guī)范,以便可以發(fā)布和收集必需的性能數(shù)據(jù)。下一節(jié)將詳細(xì)討論您可以用來完成這一工作的一些選項(xiàng)。 |
? |
分析結(jié)果。在收集應(yīng)用程序的性能數(shù)據(jù)之后,您將能夠通過確定哪些應(yīng)用程序功能要求最多的關(guān)注,來區(qū)分性能調(diào)整工作的輕重緩急。此外,您可以使用該數(shù)據(jù)來確定任何性能瓶頸的位置。通常,您將只能夠通過收集更詳細(xì)的性能數(shù)據(jù)來確定瓶頸的確切位置:例如,通過使用應(yīng)用程序規(guī)范。性能分析工具可能幫助您識(shí)別瓶頸。 |
? |
調(diào)整應(yīng)用程序。在已經(jīng)識(shí)別瓶頸之后,您可能需要修改應(yīng)用程序或其配置,以便嘗試解決問題。您應(yīng)該致力于將更改降低至最低限度,以便可以確定更改對應(yīng)用程序性能的影響。如果您同時(shí)進(jìn)行多項(xiàng)更改,可能難以確定每項(xiàng)更改對應(yīng)用程序的總體性能的影響。 |
? |
測試和度量。在更改應(yīng)用程序或其配置之后,您應(yīng)該再次測試它以確定更改具有的效果,并且使新的性能數(shù)據(jù)得以收集。性能工作通常要求進(jìn)行體系結(jié)構(gòu)或其他具有較高影響的更改,因此徹底的測試是很關(guān)鍵的。您的應(yīng)用程序測試計(jì)劃應(yīng)該針對預(yù)料到的所有情況,在配置了適當(dāng)硬件和軟件的客戶計(jì)算機(jī)上演習(xí)應(yīng)用程序所實(shí)現(xiàn)的完整范圍的功能。如果您的應(yīng)用程序使用網(wǎng)絡(luò)資源,則應(yīng)該加載這些資源,以便您可以獲得有關(guān)應(yīng)用程序在此類環(huán)境中所具有的性能的準(zhǔn)確度量。 |
上述過程將使您可以通過針對特定目標(biāo)度量應(yīng)用程序的總體性能,來重點(diǎn)解決特定的性能問題。
性能工具
您可以使用許多工具來幫助您收集和分析應(yīng)用程序的性能數(shù)據(jù)。本節(jié)中介紹的每種工具都具有不同的功能,您可以使用這些功能來度量、分析和查找應(yīng)用程序中的性能瓶頸。
注除了這里介紹的工具以外,您還可以使用其他一些選項(xiàng)和第三方工具。有關(guān)其他日志記錄和異常管理選項(xiàng)的說明,請參閱Exception Management Architecture Guide,網(wǎng)址為:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/exceptdotnet.asp
在決定哪些工具最適合您的需要之前,您應(yīng)該仔細(xì)考慮您的確切要求。
使用性能日志和警報(bào)
性能日志和警報(bào)是作為 Windows 操作系統(tǒng)的一部分發(fā)行的一種管理性能監(jiān)控工具。它依靠由各種 Windows 組件、子系統(tǒng)和應(yīng)用程序發(fā)布的性能計(jì)數(shù)器,使您可以跟蹤資源使用情況以及針對時(shí)間以圖形方式繪制它們。
您可以使用 Performance Logs and Alerts 來監(jiān)控標(biāo)準(zhǔn)的性能計(jì)數(shù)器(例如,內(nèi)存使用情況或處理器使用情況),或者您可以定義您自己的自定義計(jì)數(shù)器來監(jiān)控應(yīng)用程序特定的活動(dòng)。
.NET CLR 提供了許多有用的性能計(jì)數(shù)器,它們使您可以洞察應(yīng)用程序性能的好壞。關(guān)系比較大的一些性能對象是:
? |
.NET CLR 內(nèi)存。提供有關(guān)托管 .NET 應(yīng)用程序內(nèi)存使用情況的數(shù)據(jù),包括應(yīng)用程序正在使用的內(nèi)存數(shù)量以及對未使用的對象進(jìn)行垃圾回收所花費(fèi)的時(shí)間。 |
? |
.NET CLR 加載。提供有關(guān)應(yīng)用程序正在使用的類和應(yīng)用程序域的數(shù)量的數(shù)據(jù),并且提供有關(guān)它們的加載和卸載速率的數(shù)據(jù)。 |
? |
.NET CLR 鎖和線程。提供與應(yīng)用程序內(nèi)使用的線程有關(guān)的性能數(shù)據(jù),包括線程個(gè)數(shù)以及試圖同時(shí)對受保護(hù)的資源進(jìn)行訪問的線程之間的爭用率。 |
? |
.NET CLR 網(wǎng)絡(luò)。提供與通過網(wǎng)絡(luò)發(fā)送和接收數(shù)據(jù)有關(guān)的性能計(jì)數(shù)器,包括每秒發(fā)送和接收的字節(jié)數(shù)以及活動(dòng)連接的個(gè)數(shù)。 |
? |
.NET CLR 異常。提供有關(guān)應(yīng)用程序所引發(fā)和捕獲的異常個(gè)數(shù)的報(bào)告。 |
有關(guān)上述計(jì)數(shù)器、它們的閾值、要度量的內(nèi)容以及如何度量它們的詳細(xì)信息,請參閱 Improving .NET Application Performance and Scalability 的第 15 章“Measuring .NET Application Performance”中的“CLR and Managed Code”部分,網(wǎng)址為:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt15.asp。
您的應(yīng)用程序還可以提供您可以通過使用性能日志和警報(bào)輕松監(jiān)控的、應(yīng)用程序特定的性能計(jì)數(shù)器。您可以像以下示例所顯示的那樣,定義自定義性能計(jì)數(shù)器:
[C#]
PerformanceCounter counter = new PerformanceCounter( "Category",
"CounterName", false );
[Visual Basic .NET]
Dim counter As New PerformanceCounter("Category", "CounterName", False)
在創(chuàng)建性能計(jì)數(shù)器對象之后,您可以為您的自定義性能計(jì)數(shù)器指定類別,并將所有相關(guān)計(jì)數(shù)器保存在一起。PerformanceCounter 類在 System.Diagnostics 命名空間中定義,該命名空間中還定義了其他一些可用于讀取和定義性能計(jì)數(shù)器和類別的類。有關(guān)創(chuàng)建自定義性能計(jì)數(shù)器的詳細(xì)信息,請參閱知識(shí)庫中編號(hào)為 317679 的文章“How to create and make changes to a custom counter for the Windows Performance Monitor by using Visual Basic .NET”,網(wǎng)址為:http://support.microsoft.com/default.aspx?scid=kb;en-us;317679。
注要注冊性能計(jì)數(shù)器,您必須首先注冊該類別。您必須具有足夠的權(quán)限才能注冊性能計(jì)數(shù)器類別(它可能影響您部署應(yīng)用程序的方式)。
規(guī)范
您可以使用許多工具和技術(shù)來幫助您對應(yīng)用程序進(jìn)行規(guī)范,并且生成度量應(yīng)用程序性能所需的信息。這些工具和技術(shù)包括:
CLR Profiler
CLR Profiler 是 Microsoft 提供的一種內(nèi)存分析工具,并且可以從 MSDN 下載。它使您能夠查看應(yīng)用程序進(jìn)程的托管堆以及調(diào)查垃圾回收器的行為。使用該工具,您可以獲取有關(guān)應(yīng)用程序的執(zhí)行、內(nèi)存分配和內(nèi)存消耗的有用信息。這些信息可以幫助您了解應(yīng)用程序的內(nèi)存使用方式以及如何優(yōu)化應(yīng)用程序的內(nèi)存使用情況。
CLR Profiler 可從 http://msdn.microsoft.com/netframework/downloads/tools/default.aspx 獲得。有關(guān)如何使用 CLR Profiler 工具的詳細(xì)信息,另請參閱“How to use CLR Profiler”,網(wǎng)址為:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenethowto13.asp?frame=true。
CLR Profiler 在日志文件中記錄內(nèi)存消耗和垃圾回收器行為信息。然后,您可以使用一些不同的圖形視圖,通過 CLR Profiler 來分析該數(shù)據(jù)。一些比較重要的視圖是:
? |
Allocation Graph。顯示有關(guān)對象分配方式的調(diào)用堆棧。您可以使用該視圖來查看方法進(jìn)行的每個(gè)分配的系統(tǒng)開銷,隔離您不希望發(fā)生的分配,以及查看方法可能進(jìn)行的過度分配。 |
? |
Assembly, Module, Function, and Class Graph。顯示哪些方法造成了哪些程序集、函數(shù)、模塊或類的加載。 |
? |
Call Graph。使您可以查看哪些方法調(diào)用了其他哪些方法以及相應(yīng)的調(diào)用頻率。您可以使用該圖表來確定庫調(diào)用的系統(tǒng)開銷,以及調(diào)用了哪些方法或?qū)μ囟ǚ椒ㄟM(jìn)行了多少個(gè)調(diào)用。 |
? |
Time Line。提供了有關(guān)應(yīng)用程序執(zhí)行的基于文本的、按時(shí)間順序的層次結(jié)構(gòu)視圖。使用該視圖可以查看分配了哪些類型以及這些類型的大小。您還可以使用該視圖查看方法調(diào)用使得哪些程序集被加載,并且分析您不希望發(fā)生的分配。您可以分析完成器的使用情況,并且識(shí)別尚未實(shí)現(xiàn)或調(diào)用 Close 或 Dispose 從而導(dǎo)致瓶頸的方法。 |
您可以使用 CLR Profiler.exe 來識(shí)別和隔離與垃圾回收有關(guān)的問題。這包括內(nèi)存消耗問題(例如,過度或未知的分配、內(nèi)存泄漏、生存期很長的對象)以及在執(zhí)行垃圾回收時(shí)花費(fèi)的時(shí)間的百分比。
注有關(guān)如何使用 CLR Profiler 工具的詳細(xì)信息,請參閱“Improving .NET Application Performance and Scalability”,網(wǎng)址為:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenethowto13.asp?frame=true。
小結(jié)
要完全實(shí)現(xiàn)智能客戶端應(yīng)用程序的潛能,您需要在應(yīng)用程序的設(shè)計(jì)階段仔細(xì)考慮性能問題。通過在早期階段解決這些性能問題,您可以在應(yīng)用程序設(shè)計(jì)過程中控制成本,并減小在開發(fā)周期的后期陷入性能問題的可能性。
本章分析了許多不同的技術(shù),您可以在規(guī)劃和設(shè)計(jì)智能客戶端應(yīng)用程序時(shí)使用這些技術(shù),以確保優(yōu)化它們的性能。本章還考察了您可以用來確定智能客戶端應(yīng)用程序內(nèi)的性能問題的一些工具和技術(shù)。
參考資料
有關(guān)詳細(xì)信息,請參閱以下內(nèi)容:
轉(zhuǎn)到原英文頁面