第一篇:總結(jié)使用Unity3D優(yōu)化游戲運行性能的經(jīng)驗
流暢的游戲玩法來自流暢的幀率,而我們即將推出的動作平臺游戲《Shadow Blade》已經(jīng)將在標(biāo)準(zhǔn)iPhone和iPad設(shè)備上實現(xiàn)每秒60幀視為一個重要目標(biāo)。
以下是我們在緊湊的優(yōu)化過程中提升游戲運行性能,并實現(xiàn)目標(biāo)幀率時需要考慮的事項。--來自狗刨學(xué)習(xí)網(wǎng)
當(dāng)基本游戲功能到位時,就要確保游戲運行表現(xiàn)能夠達標(biāo)。我們衡量游戲運行表現(xiàn)的一個基本工具是Unity內(nèi)置分析器以及Xcode分析工具。使用Unity分析器來分析設(shè)備上的運行代碼真是一項寶貴的功能。
我們總結(jié)了這種為將目標(biāo)設(shè)備的幀率控制在60fps而進行衡量、調(diào)整、再衡量過程的中相關(guān)經(jīng)
一、遇到麻煩時要調(diào)用“垃圾回收器”(Garbage Collector,無用單元收集程序,以下簡稱GC)
由于具有C/C++游戲編程背景,我們并不習(xí)慣無用單元收集程序的特定行為。確保自動清理你不用的內(nèi)存,這種做法在剛開始時很好,但很快你就公發(fā)現(xiàn)自己的分析器經(jīng)常顯示CPU負荷過大,原因是垃圾回收器正在收集垃圾內(nèi)存。這對移動設(shè)備來說尤其是個大問題。要跟進內(nèi)存分配,并盡量避免它們成為優(yōu)先數(shù),以下是我們應(yīng)該采取的主要操作:
1.移除代碼中的任何字符串連接,因為這會給GC留下大量垃圾。
2.用簡單的“for”循環(huán)代替“foreach”循環(huán)。由于某些原因,每個“foreach”循環(huán)的每次迭代會生成24字節(jié)的垃圾內(nèi)存。一個簡單的循環(huán)迭代10次就可以留下240字節(jié)的垃圾內(nèi)存。
3.更改我們檢查游戲?qū)ο髽?biāo)簽的方法。用“if(go.CompareTag(“Enemy”)”來代替“if(go.tag == “Enemy”)”。在一個內(nèi)部循環(huán)調(diào)用對象分配的標(biāo)簽屬性以及拷貝額外內(nèi)存,這是一個非常糟糕的做法。
4.對象庫很棒,我們?yōu)樗袆討B(tài)游戲?qū)ο笾谱骱褪褂脦欤@樣在游戲運行時間內(nèi)不會動態(tài)分配任何東西,不需要的時候所有東西反向循環(huán)到庫中。
5.不使用LINQ命令,因為它們一般會分配中間緩器,而這很容易生成垃圾內(nèi)存。
二、謹慎處理高級腳本和本地引擎C++代碼之間的通信開銷。
所有使用Unity3D編寫的游戲玩法代碼都是腳本代碼,在我們的項目中是使用Mono執(zhí)行時間處理的C#代碼。任何與引擎數(shù)據(jù)的通信需求都要有一個進入高級腳本語言的本地引擎代碼的調(diào)用。這當(dāng)然會產(chǎn)生它自己的開銷,而盡量減少游戲代碼中的這些調(diào)用則要排在第二位。
1.在這一情景中四處移動對象要求來自腳本代碼的調(diào)用進入引擎代碼,這樣我們就會在游戲玩法代碼的一個幀中緩存某一對象的轉(zhuǎn)換需求,并一次僅向引擎發(fā)送一個請求,以便減少調(diào)用開銷。這種模式也適用于其他相似的地方,而不僅局限于移動和旋轉(zhuǎn)對象。
2.將引用本地緩存到元件中會減少每次在一個游戲?qū)ο笾惺褂?“GetComponent” 獲取一個元件引用的需求,這是調(diào)用本地引擎代碼的另一個例子。
三、物理效果
1.將物理模擬時間步設(shè)置到最小化狀態(tài)。在我們的項目中就不可以將讓它低于16毫秒。
2.減少角色控制器移動命令的調(diào)用。移動角色控制器會同步發(fā)生,每次調(diào)用都會耗損極大的性能。我們的做法是緩存每幀的移動請求,并且僅運用一次。
3.修改代碼以免依賴“ControllerColliderHit” 回調(diào)函數(shù)。這證明這些回調(diào)函數(shù)處理得并不十分迅速。
4.面對性能更弱的設(shè)備,要用skinned mesh代替physics cloth。cloth參數(shù)在運行表現(xiàn)中發(fā)揮重要作用,如果你肯花些時間找到美學(xué)與運行表現(xiàn)之間的平衡點,就可以獲得理想的結(jié)果。
5.在物理模擬過程中不要使用ragdolls,只有在必要時才讓它生效。
6.要謹慎評估觸發(fā)器的“onInside”回調(diào)函數(shù),在我們的項目中,我們盡量在不依賴它們的情況下模擬邏輯。
7.使用層次而不是標(biāo)簽。我們可以輕松為對象分配層次和標(biāo)簽,并查詢特定對象,但是涉及碰撞邏輯時,層次至少在運行表現(xiàn)上會更有明顯優(yōu)勢。更快的物理計算和更少的無用分配內(nèi)存是使用層次的基本原因。
8.千萬不要使用Mesh對撞機。
9.最小化碰撞檢測請求(例如ray casts和sphere checks),盡量從每次檢查中獲得更多信息。
四、讓AI代碼更迅速
我們使用AI敵人來阻攔忍者英雄,并同其過招。以下是與AI性能問題有關(guān)的一些建議:
1.AI邏輯(例如能見度檢查等)會生成大量物理查詢??梢宰孉I更新循環(huán)設(shè)置低于圖像更新循環(huán),以減少CPU負荷。
五、最佳性能表現(xiàn)根本就不是來自代碼!
沒有發(fā)生什么情況的時候,就說明性能良好。這是我們關(guān)閉一切不必要之物的基本原則。我們的項目是一個側(cè)邊橫向卷軸動作游戲,所以如果不具有可視性時,就可以關(guān)閉許多動態(tài)關(guān)卡物體。
1.使用細節(jié)層次的定制關(guān)卡將遠處的敵人AI關(guān)閉。
2.移動平臺和障礙,當(dāng)它們遠去時其物理碰撞機也會關(guān)閉。
3.Unity內(nèi)置的“動畫挑選”系統(tǒng)可以用來關(guān)閉未被渲染對象的動畫。
4.所有關(guān)卡內(nèi)的粒子系統(tǒng)也可以使用同樣的禁用機制。
六、回調(diào)函數(shù)!那么空白的回調(diào)函數(shù)呢?
要盡量減少Unity回調(diào)函數(shù)。即使敵人回調(diào)函數(shù)存在性能損失。沒有必要將空白的回調(diào)函數(shù)留在代碼庫中(有時候介于大量代碼重寫和重構(gòu)之間)。
七、讓美術(shù)人員來救場
在程序員抓耳撓腮,絞盡腦汁去想該如何讓每秒運行更多幀時,美術(shù)人員總能神奇地派上大用場。
1.共享游戲?qū)ο蟛牧?,令其在Unity中處于靜止?fàn)顟B(tài),可以讓它們綁定在一起,由此產(chǎn)生的簡化繪圖調(diào)用是呈現(xiàn)良好移動運行性能的重要元素。
2.紋理地圖集對UI元素來說尤其有用。
3.方形紋理以及兩者功率的合理壓縮是必不可少的步驟。
4.我們的美術(shù)人員移除了所有遠處背景的網(wǎng)格,并將其轉(zhuǎn)化為簡單的2D位面。
5.光照圖非常有價值。
6.我們的美術(shù)人員在一些關(guān)口移除了額外頂點。
7.使用合理的紋理mip標(biāo)準(zhǔn)是一個好主意(注:要讓不同分辨率的設(shè)備呈現(xiàn)良好的幀率時尤其如此)。
8.結(jié)合網(wǎng)格是美術(shù)人員可以發(fā)揮作用的另一個操作。
9.我們的動畫師盡力讓不同角色共享動畫。
10.要找到美學(xué)/性能之間的平衡,就免不了許多粒子效果的迭代。減少發(fā)射器數(shù)量并盡量減少透明度需求也是一大挑戰(zhàn)。
八、要減少內(nèi)存使用
使用大內(nèi)存當(dāng)然會對性能產(chǎn)生負面影響,但在我們的項目中,我們的iPod由于超過內(nèi)存上限而遭遇了多次崩潰事件。我們的游戲中最耗內(nèi)存的是紋理。
1.不同設(shè)備要使用不同的紋理大小,尤其是UI和大型背景中的紋理?!禨hadow Blade》使用的是通用型模板,但如果在啟動時檢測到設(shè)備大小和分辨率,就會載入不同資產(chǎn)。
2.我們要確保未使用的資產(chǎn)不會載入內(nèi)存。我們必須遲一點在項目中找到僅被一個預(yù)制件實例引用,并且從未完全載入內(nèi)存中實例化的資產(chǎn)。
3.去除網(wǎng)格中的額外多邊形也能實現(xiàn)這一點。
4.我們應(yīng)該重建一些資產(chǎn)的生周期管理。例如,調(diào)整主菜單資產(chǎn)的加載/卸載時間,或者關(guān)卡資產(chǎn)、游戲音樂的有效期限。
5.每個關(guān)卡都要有根據(jù)其動態(tài)對象需求而量身定制的特定對象庫,并根據(jù)最小內(nèi)存需求來優(yōu)化。對象庫可以靈活一點,在開發(fā)過程中包含大量對象,但知道游戲?qū)ο笮枨蠛缶鸵唧w一點。
6.保持聲音文件在內(nèi)存的壓縮狀態(tài)也是必要之舉。
加強游戲運行性能是一個漫長而具有挑戰(zhàn)性的過程,游戲開發(fā)社區(qū)所分享的大量知識,以及Unity提供的出色分析工具為《Shadow Blade》實現(xiàn)目標(biāo)運行性能提供了極大幫助。
第二篇:計算機系統(tǒng)性能優(yōu)化總結(jié)
計算機系統(tǒng)性能優(yōu)化總結(jié)
現(xiàn)今,計算機技術(shù)在社會各行各業(yè)都得到了廣泛的應(yīng)用。計算機給我們的學(xué)習(xí)、生活和工作都帶來了極大的便利。但隨著我們對計算機整體性能要求的提高,計算機系統(tǒng)性能的優(yōu)化就顯得尤為重要。
一、計算機系統(tǒng)運行不佳的原因分析
計算機系統(tǒng)運行性能不佳的原因有很多。如,系統(tǒng)平臺結(jié)構(gòu)不好、系統(tǒng)配置不好或參數(shù)設(shè)置不對;應(yīng)用系統(tǒng)數(shù)據(jù)結(jié)構(gòu)設(shè)計不合理,加大了系統(tǒng)的輸入和輸出需求;應(yīng)用系統(tǒng)算法或邏輯處理有問題,使計算機系統(tǒng)達不到最佳的運行狀態(tài)。
二、計算機系統(tǒng)性能優(yōu)化措施
1.合理地配置各種軟件,使計算機系統(tǒng)發(fā)揮最好的功能。計算機系統(tǒng)由硬件系統(tǒng)和軟件系統(tǒng)組成,二者之間相互依賴,這就要求我們在使用計算機軟件的過程中,使用一些速度較快、版本較高和功能較完善的軟件,并仔細閱讀各種軟件的使用說明,避免在應(yīng)用過程中發(fā)生沖突。作為編程人員,在編寫應(yīng)用程序的過程中,要充分考慮應(yīng)用系統(tǒng)數(shù)據(jù)結(jié)構(gòu)設(shè)計的合理性,以便使計算機系統(tǒng)達到最佳的運行狀態(tài)。
2.調(diào)整輸入和輸出系統(tǒng)。在計算機系統(tǒng)的應(yīng)用過程中,我們進行的大多數(shù)操作就是輸入和輸出。因此,輸入和輸出操作是影響計算機性能的一個重要因素。隨著科技的日益發(fā)
展,磁盤的平均尋址時間日益縮短,但與中央處理器的運算相比,仍然緩慢很多。在觀察一些系統(tǒng)運行時,經(jīng)常出現(xiàn)中央處理器處在空閑狀態(tài)而應(yīng)用程序卻遲遲不能完成的情況。究其原因,就是因為磁盤的輸入和輸出的速度太慢,數(shù)據(jù)沒有讀(寫)入內(nèi)存中。因此,在實際的應(yīng)用過程中,我們可以考慮把數(shù)據(jù)文件存放在不同的磁盤上,讓多個磁盤并行工作,從而解決輸入和輸出的瓶頸問題。如果輸入和輸出總數(shù)明顯不合理,就要考慮查找引起輸入和輸出數(shù)量增大的原因,從而優(yōu)化應(yīng)用程序,減少輸入和輸出的次數(shù),提高系統(tǒng)的性能。
3.安排相同性質(zhì)的處理過程同時運行,以確保中央處理器和輸入和輸出的絕對通暢。一臺計算機能夠同時運行多個應(yīng)用程序,從使用系統(tǒng)資源的角度來看,這些應(yīng)用程序可以分為面向輸入和輸出與面向運算2種類型。
系統(tǒng)中如果有2個或多個面向輸入和輸出的應(yīng)用在同時運行,就會造成中央處理器閑置而大量磁盤輸入和輸出擁塞和等待的情況,使得各個應(yīng)用程序的性能變差。系統(tǒng)中如果有2個或多個面向運算的應(yīng)用程序同時運行時,就會造成磁盤空轉(zhuǎn)的情況。因此,要盡量避免讓多個面向輸入和輸出或多個面向運算的應(yīng)用程序同時運行。最好的安排就是讓面向輸入和輸出與面向運算的應(yīng)用程序合理搭配,使每個應(yīng)用都能獲得足夠的系統(tǒng)服務(wù)而又互不影響。
4.合理地使用中央處理器。一般來說,在一個計算機系統(tǒng)中,中央處理器的速度要遠遠高于輸入和輸出的速度,因而輸入和輸出速度往往是影響系統(tǒng)性能的主要因素。但必須指出的是,這種規(guī)則只適用于普通的情況。如果不知道中央處理器能力也有一定限制,盲目地、不合理地使用中央處理器,中央處理器也會成為影響系統(tǒng)性能的主要因素。
通過對計算機系統(tǒng)的性能進行優(yōu)化,排除了系統(tǒng)中的各種不合理因素,縮短了系統(tǒng)的響應(yīng)時間,使計算機系統(tǒng)能更好地發(fā)揮作用,從而為我們提供更好的服務(wù)。
第三篇:網(wǎng)站前端性能優(yōu)化總結(jié)
一、服務(wù)器側(cè)優(yōu)化
1.添加 Expires 或 Cache-Control 信息頭
某些經(jīng)常使用到、并且不會經(jīng)常做改動的圖片(banner、logo等等)、靜態(tài)文件(登錄首頁、說明文檔等)可以設(shè)置較長的有效期(expiration date),這些HTTP頭向客戶端表明了文檔的有效性和持久性。如果有緩存,文檔就可以從緩存(除已經(jīng)過期)而不是從服務(wù)器讀取。接著,客戶端考察緩存中的副本,看看是否過期或者失效,以決定是否必須從服務(wù)器獲得更新。
各個容器都有針對的方案,,以 Apache 為例:
ExpiresActive On ExpiresByType image/gif “access plus 1 weeks”
表示gif文件緩存一周,配置可以根據(jù)具體的業(yè)務(wù)進行調(diào)整,具體配置可以參考:http://lamp.linux.gov.cn/Apache/ApacheMenu/mod/mod_expires.html
2.壓縮內(nèi)容
對于絕大多數(shù)站點,這都是必要的一步,能有效減輕網(wǎng)絡(luò)流量壓力。
表示zlib在壓縮時可以最大程度的使用內(nèi)存,壓縮html、文本、xml和php這幾種類型的文件,指定擴展名為html、htm、xml、php、css和js的文件啟用壓縮。
具體配置可以參考:http://lamp.linux.gov.cn/Apache/ApacheMenu/mod/mod_deflate.html
3.設(shè)置 Etags
在使用etags之前,有必要復(fù)習(xí)一下 RFC2068 中規(guī)定的返回值 200 和 304 的含義:
200--OK 304--Not Modified
客戶端在請求一份文件的時候,服務(wù)端會檢查客戶端是否存在該文件,如果客戶端不存在該文件,則下載該文件并返回200;如果客戶端存在該文件并且該文件在規(guī)定期限內(nèi)沒有被修改(Inode,MTime和Size),則服務(wù)端只返回一個304,并不返回資源內(nèi)容,客戶端將會使用之前的緩存文件。而etags就是判斷該文件是否被修改的記號,與服務(wù)器端的資源一一關(guān)聯(lián),所以etags對于CGI類型的頁面緩存尤其有用。
下圖是優(yōu)化前的首頁:(注意,此時沒有壓縮首頁圖片,即使使用了緩存,仍需要5s左右的時間)
化前的某頁面
需要注意的是,使用etags會增加服務(wù)器端的負載,在實際應(yīng)用中需要自行平衡。
二、Cookie優(yōu)化
1.減小Cookie體積
HTTP coockie可以用于權(quán)限驗證和個性化身份等多種用途。coockie內(nèi)的有關(guān)信息是通過HTTP文件頭來在web服務(wù)器和瀏覽器之間進行交流的。因此保持coockie盡可能的小以減少用戶的響應(yīng)時間十分重要。
使cookie體積盡量?。?/p>
在合適的子域名上設(shè)置bookie,以免影響其他子域名下的響應(yīng);
設(shè)置合理的過期時間,去掉不必要的cookie。
下面對比一下各個網(wǎng)站的cookie:
圖中可以看出,6K的cookie顯然是不必要的。
2.對于頁面內(nèi)容使用無coockie域名
當(dāng)瀏覽器在請求中同時請求一張靜態(tài)的圖片和發(fā)送coockie時,服務(wù)器對于這些coockie不會做任何地使用。因此它們只是因為某些負面因素而創(chuàng)建的網(wǎng)絡(luò)傳輸。所以你應(yīng)該確定對于靜態(tài)內(nèi)容的請求是無coockie的請求。創(chuàng)建一個子域名并用他來存放所有靜態(tài)內(nèi)容。
例如,域名是
3.切分組件到多個域
主要的目的是提高頁面組件并行下載能力,但注意,也不要同時使用過多的域名,否則就會出現(xiàn)第一條DNS lookup過多的問題,一般情況下兩個域名就可以了。
4.杜絕 http 404 錯誤
對頁面鏈接的充分測試加上對 Web 服務(wù)器 error 日志的不斷跟蹤可以有效減少 404 錯誤,并提升用戶體驗。
后記:
這次總結(jié)給我?guī)淼膯l(fā)并不在于提升系統(tǒng)性能性能本身,提升性能只是一個很表面上的東西,網(wǎng)上的方法有很多,測試的方法也有很多,照著都做一遍,性能確實會有所提升,但是這種知其然而不知其所以然的性能提升是沒有意義的,這便是本文的目的所在。
第四篇:心得總結(jié):Java性能優(yōu)化技巧集錦
心得總結(jié):Java性能優(yōu)化技巧集錦
一、通用篇
“通用篇”討論的問題適合于大多數(shù)Java應(yīng)用。
1.1 不用new關(guān)鍵詞創(chuàng)建類的實例
用new關(guān)鍵詞創(chuàng)建類的實例時,構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會被自動調(diào)用。但如果一個對象實現(xiàn)了Cloneable接口,我們可以調(diào)用它的clone()方法。clone()方法不會調(diào)用任何類構(gòu)造函數(shù)。
在使用設(shè)計模式(Design Pattern)的場合,如果用Factory模式創(chuàng)建對象,則改用clone()方法創(chuàng)建新的對象實例非常簡單。例如,下面是Factory模式的一個典型實現(xiàn): public static Credit getNewCredit(){ return new Credit();}
改進后的代碼使用clone()方法,如下所示: private static Credit BaseCredit = new Credit();public static Credit getNewCredit(){ return(Credit)BaseCredit.clone();}
上面的思路對于數(shù)組處理同樣很有用。
1.2 使用非阻塞I/O
版本較低的JDK不支持非阻塞I/O API。為避免I/O阻塞,一些應(yīng)用采用了創(chuàng)建大量線程的辦法(在較好的情況下,會使用一個緩沖池)。這種技術(shù)可以在許多必須支持并發(fā)I/O流的應(yīng)用中見到,如Web服務(wù)器、報價和拍賣應(yīng)用等。然而,創(chuàng)建Java線程需要相當(dāng)可觀的開銷。
JDK 1.4引入了非阻塞的I/O庫(java.nio)。如果應(yīng)用要求使用版本較早的JDK,在這里有一個支持非阻塞I/O的軟件包。
請參見Sun中國網(wǎng)站的《調(diào)整Java的I/O性能》。
1.3 慎用異常
異常對性能不利。拋出異常首先要創(chuàng)建一個新的對象。Throwable接口的構(gòu)造函數(shù)調(diào)用名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆棧,收集調(diào)用跟蹤信息。只要有異常被拋出,VM就必須調(diào)整調(diào)用堆棧,因為在處理過程中創(chuàng)建了一個新的對象。
異常只能用于錯誤處理,不應(yīng)該用來控制程序流程。
1.4 不要重復(fù)初始化變量
默認情況下,調(diào)用類的構(gòu)造函數(shù)時,Java會把變量初始化成確定的值:所有的對象被設(shè)置成null,整數(shù)變量(byte、short、int、long)設(shè)置成0,float和 double變量設(shè)置成0.0,邏輯值設(shè)置成false。當(dāng)一個類從另一個類派生時,這一點尤其應(yīng)該注意,因為用new關(guān)鍵詞創(chuàng)建一個對象時,構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會被自動調(diào)用。
1.5 盡量指定類的final修飾符
帶有final修飾符的類是不可派生的。在Java核心API中,有許多應(yīng)用final的例子,例如java.lang.String。為String類指定final防止了人們覆蓋length()方法。
另外,如果指定一個類為final,則該類所有的方法都是final。Java編譯器會尋找機會內(nèi)聯(lián)(inline)所有的final方法(這和具體的編譯器實現(xiàn)有關(guān))。此舉能夠使性能平均提高50%。
1.6 盡量使用局部變量
調(diào)用方法時傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時變量都保存在棧(Stack)中,速度較快。其他變量,如靜態(tài)變量、實例變量等,都在堆(Heap)中創(chuàng)建,速度較慢。另外,依賴于具體的編譯器/JVM,局部變量還可能得到進一步優(yōu)化。請參見《盡可能使用堆棧變量》。
1.7 乘法和除法
考慮下面的代碼:
for(val = 0;val < 100000;val +=5){ alterX = val * 8;myResult = val * 2;}
用移位操作替代乘法操作可以極大地提高性能。下面是修改后的代碼:
for(val = 0;val < 100000;val += 5){ alterX = val << 3;myResult = val << 1;}
修改后的代碼不再做乘以8的操作,而是改用等價的左移3位操作,每左移1位相當(dāng)于乘以2。相應(yīng)地,右移1位操作相當(dāng)于除以2。值得一提的是,雖然移位操作速度快,但可能使代碼比較難于理解,所以最好加上一些注釋。
二、J2EE篇
前面介紹的改善性能技巧適合于大多數(shù)Java應(yīng)用,接下來要討論的問題適合于使用JSP、EJB或JDBC的應(yīng)用。
2.1 使用緩沖標(biāo)記
一些應(yīng)用服務(wù)器加入了面向JSP的緩沖標(biāo)記功能。例如,BEA的WebLogic Server從6.0版本開始支持這個功能,Open Symphony工程也同樣支持這個功能。JSP緩沖標(biāo)記既能夠緩沖頁面片斷,也能夠緩沖整個頁面。當(dāng)JSP頁面執(zhí)行時,如果目標(biāo)片斷已經(jīng)在緩沖之中,則生成該片斷的代碼就不用再執(zhí)行。頁面級緩沖捕獲對指定URL的請求,并緩沖整個結(jié)果頁面。對于購物籃、目錄以及門戶網(wǎng)站的主頁來說,這個功能極其有用。對于這類應(yīng)用,頁面級緩沖能夠保存頁面執(zhí)行的結(jié)果,供后繼請求使用。
對于代碼邏輯復(fù)雜的頁面,利用緩沖標(biāo)記提高性能的效果比較明顯;反之,效果可能略遜一籌。
請參見《用緩沖技術(shù)提高JSP應(yīng)用的性能和穩(wěn)定性》。
2.2 始終通過會話Bean訪問實體Bean
直接訪問實體Bean不利于性能。當(dāng)客戶程序遠程訪問實體Bean時,每一個get方法都是一個遠程調(diào)用。訪問實體Bean的會話Bean是本地的,能夠把所有數(shù)據(jù)組織成一個結(jié)構(gòu),然后返回它的值。
用會話Bean封裝對實體Bean的訪問能夠改進事務(wù)管理,因為會話Bean只有在到達事務(wù)邊界時才會提交。每一個對get方法的直接調(diào)用產(chǎn)生一個事務(wù),容器將在每一個實體Bean的事務(wù)之后執(zhí)行一個“裝入-讀取”操作。
一些時候,使用實體Bean會導(dǎo)致程序性能不佳。如果實體Bean的唯一用途就是提取和更新數(shù)據(jù),改成在會話Bean之內(nèi)利用JDBC訪問數(shù)據(jù)庫可以得到更好的性能。
2.3 選擇合適的引用機制
在典型的JSP應(yīng)用系統(tǒng)中,頁頭、頁腳部分往往被抽取出來,然后根據(jù)需要引入頁頭、頁腳。當(dāng)前,在JSP頁面中引入外部資源的方法主要有兩種:include指令,以及include動作。
include指令:例如<%@ include file=“copyright.html” %>。該指令在編譯時引入指定的資源。在編譯之前,帶有include指令的頁面和指定的資源被合并成一個文件。被引用的外部資源在編譯時就確定,比運行時才確定資源更高效。
include動作:例如
2.4 在部署描述器中設(shè)置只讀屬性
實體Bean的部署描述器允許把所有g(shù)et方法設(shè)置成“只讀”。當(dāng)某個事務(wù)單元的工作只包含執(zhí)行讀取操作的方法時,設(shè)置只讀屬性有利于提高性能,因為容器不必再執(zhí)行存儲操作。
2.5 緩沖對EJB Home的訪問
EJB Home接口通過JNDI名稱查找獲得。這個操作需要相當(dāng)可觀的開銷。JNDI查找最好放入Servlet的init()方法里面。如果應(yīng)用中多處頻繁地出現(xiàn)EJB訪問,最好創(chuàng)建一個EJBHomeCache類。EJBHomeCache類一般應(yīng)該作為singleton實現(xiàn)。
2.6 為EJB實現(xiàn)本地接口
本地接口是EJB 2.0規(guī)范新增的內(nèi)容,它使得Bean能夠避免遠程調(diào)用的開銷。請考慮下面的代碼。PayBeanHome home =(PayBeanHome)javax.rmi.PortableRemoteObject.narrow(ctx.lookup(“PayBeanHome”), PayBeanHome.class);PayBean bean =(PayBean)javax.rmi.PortableRemoteObject.narrow(home.create(), PayBean.class);
第一個語句表示我們要尋找Bean的Home接口。這個查找通過JNDI進行,它是一個RMI調(diào)用。然后,我們定位遠程對象,返回代理引用,這也是一個 RMI調(diào)用。第二個語句示范了如何創(chuàng)建一個實例,涉及了創(chuàng)建IIOP請求并在網(wǎng)絡(luò)上傳輸請求的stub程序,它也是一個RMI調(diào)用。
要實現(xiàn)本地接口,我們必須作如下修改:
方法不能再拋出java.rmi.RemoteException異常,包括從RemoteException派生的異常,比如 TransactionRequiredException、TransactionRolledBackException和 NoSuchObjectException。EJB提供了等價的本地異常,如TransactionRequiredLocalException、TransactionRolledBackLocalException和NoSuchObjectLocalException。
所有數(shù)據(jù)和返回值都通過引用的方式傳遞,而不是傳遞值。
本地接口必須在EJB部署的機器上使用。簡而言之,客戶程序和提供服務(wù)的組件必須在同一個JVM上運行。
如果Bean實現(xiàn)了本地接口,則其引用不可串行化。
請參見《用本地引用提高EJB訪問效率》。
軟件資訊 > 開發(fā)特區(qū) > 開發(fā)語言 > Java
2006年05月11日 作者:songxin 責(zé)任編輯:xietaoming
文章導(dǎo)讀:分通用篇、J2EE篇、GUI篇三部分介紹Java性能優(yōu)化技巧。
2.7 生成主鍵
在EJB之內(nèi)生成主鍵有許多途徑,下面分析了幾種常見的辦法以及它們的特點。
利用數(shù)據(jù)庫內(nèi)建的標(biāo)識機制(SQL Server的IDENTITY或Oracle的SEQUENCE)。這種方法的缺點是EJB可移植性差。
由實體Bean自己計算主鍵值(比如做增量操作)。它的缺點是要求事務(wù)可串行化,而且速度也較慢。
利用NTP之類的時鐘服務(wù)。這要求有面向特定平臺的本地代碼,從而把Bean固定到了特定的OS之上。另外,它還導(dǎo)致了這樣一種可能,即在多CPU的服務(wù)器上,同一個毫秒之內(nèi)生成了兩個主鍵。
借鑒Microsoft的思路,在Bean中創(chuàng)建一個GUID。然而,如果不求助于JNI,Java不能確定網(wǎng)卡的MAC地址;如果使用JNI,則程序就要依賴于特定的OS。
還有其他幾種辦法,但這些辦法同樣都有各自的局限。似乎只有一個答案比較理想:結(jié)合運用RMI和JNDI。先通過RMI注冊把RMI遠程對象綁定到JNDI樹??蛻舫绦蛲ㄟ^JNDI進行查找。下面是一個例子: public class keyGenerator extends UnicastRemoteObject implements Remote { private static long KeyValue = System.currentTimeMillis();public static synchronized long getKey()throws RemoteException { return KeyValue++;}
2.8 及時清除不再需要的會話
為了清除不再活動的會話,許多應(yīng)用服務(wù)器都有默認的會話超時時間,一般為30分鐘。當(dāng)應(yīng)用服務(wù)器需要保存更多會話時,如果內(nèi)存容量不足,操作系統(tǒng)會把部分內(nèi)存數(shù)據(jù)轉(zhuǎn)移到磁盤,應(yīng)用服務(wù)器也可能根據(jù)“最近最頻繁使用”(Most Recently Used)算法把部分不活躍的會話轉(zhuǎn)儲到磁盤,甚至可能拋出“內(nèi)存不足”異常。在大規(guī)模系統(tǒng)中,串行化會話的代價是很昂貴的。當(dāng)會話不再需要時,應(yīng)當(dāng)及時調(diào)用HttpSession.invalidate()方法清除會話。HttpSession.invalidate()方法通常可以在應(yīng)用的退出頁面調(diào)用。
2.9 在JSP頁面中關(guān)閉無用的會話
對于那些無需跟蹤會話狀態(tài)的頁面,關(guān)閉自動創(chuàng)建的會話可以節(jié)省一些資源。使用如下page指令: <%@ page session=“false”%>
2.10 Servlet與內(nèi)存使用
許多開發(fā)者隨意地把大量信息保存到用戶會話之中。一些時候,保存在會話中的對象沒有及時地被垃圾回收機制回收。從性能上看,典型的癥狀是用戶感到系統(tǒng)周期性地變慢,卻又不能把原因歸于任何一個具體的組件。如果監(jiān)視JVM的堆空間,它的表現(xiàn)是內(nèi)存占用不正常地大起大落。
解決這類內(nèi)存問題主要有二種辦法。第一種辦法是,在所有作用范圍為會話的Bean中實現(xiàn)HttpSessionBindingListener接口。這樣,只要實現(xiàn)valueUnbound()方法,就可以顯式地釋放Bean使用的資源。
另外一種辦法就是盡快地把會話作廢。大多數(shù)應(yīng)用服務(wù)器都有設(shè)置會話作廢間隔時間的選項。另外,也可以用編程的方式調(diào)用會話的 setMaxInactiveInterval()方法,該方法用來設(shè)定在作廢會話之前,Servlet容器允許的客戶請求的最大間隔時間,以秒計。
2.11 HTTP Keep-Alive
Keep-Alive功能使客戶端到服務(wù)器端的連接持續(xù)有效,當(dāng)出現(xiàn)對服務(wù)器的后繼請求時,Keep-Alive功能避免了建立或者重新建立連接。市場上的大部分Web服務(wù)器,包括iPlanet、IIS和Apache,都支持HTTP Keep-Alive。對于提供靜態(tài)內(nèi)容的網(wǎng)站來說,這個功能通常很有用。但是,對于負擔(dān)較重的網(wǎng)站來說,這
里存在另外一個問題:雖然為客戶保留打開的連接有一定的好處,但它同樣影響了性能,因為在處理暫停期間,本來可以釋放的資源仍舊被占用。當(dāng)Web服務(wù)器和應(yīng)用服務(wù)器在同一臺機器上運行時,Keep-Alive功能對資源利用的影響尤其突出。
2.12 JDBC與Unicode
想必你已經(jīng)了解一些使用JDBC時提高性能的措施,比如利用連接池、正確地選擇存儲過程和直接執(zhí)行的SQL、從結(jié)果集刪除多余的列、預(yù)先編譯SQL語句,等等。
除了這些顯而易見的選擇之外,另一個提高性能的好選擇可能就是把所有的字符數(shù)據(jù)都保存為Unicode(代碼頁13488)。Java以Unicode形式處理所有數(shù)據(jù),因此,數(shù)據(jù)庫驅(qū)動程序不必再執(zhí)行轉(zhuǎn)換過程。但應(yīng)該記?。喝绻捎眠@種方式,數(shù)據(jù)庫會變得更大,因為每個Unicode字符需要2個字節(jié)存儲空間。另外,如果有其他非Unicode的程序訪問數(shù)據(jù)庫,性能問題仍舊會出現(xiàn),因為這時數(shù)據(jù)庫驅(qū)動程序仍舊必須執(zhí)行轉(zhuǎn)換過程。
2.13 JDBC與I/O
如果應(yīng)用程序需要訪問一個規(guī)模很大的數(shù)據(jù)集,則應(yīng)當(dāng)考慮使用塊提取方式。默認情況下,JDBC每次提取32行數(shù)據(jù)。舉例來說,假設(shè)我們要遍歷一個5000 行的記錄集,JDBC必須調(diào)用數(shù)據(jù)庫157次才能提取到全部數(shù)據(jù)。如果把塊大小改成512,則調(diào)用數(shù)據(jù)庫的次數(shù)將減少到10次。
在一些情形下這種技術(shù)無效。例如,如果使用可滾動的記錄集,或者在查詢中指定了FOR UPDATE,則塊操作方式不再有效。
2.14 內(nèi)存數(shù)據(jù)庫
許多應(yīng)用需要以用戶為單位在會話對象中保存相當(dāng)數(shù)量的數(shù)據(jù),典型的應(yīng)用如購物籃和目錄等。由于這類數(shù)據(jù)可以按照行/列的形式組織,因此,許多應(yīng)用創(chuàng)建了龐大的Vector或HashMap。在會話中保存這類數(shù)據(jù)極大地限制了應(yīng)用的可伸縮性,因為服務(wù)器擁有的內(nèi)存至少必須達到每個會話占用的內(nèi)存數(shù)量乘以并發(fā)用戶最大數(shù)量,它不僅使服務(wù)器價格昂貴,而且垃圾收集的時間間隔也可能延長到難以忍受的程度。
一些人把購物籃/目錄功能轉(zhuǎn)移到數(shù)據(jù)庫層,在一定程度上提高了可伸縮性。然而,把這部分功能放到數(shù)據(jù)庫層也存在問題,且問題的根源與大多數(shù)關(guān)系數(shù)據(jù)庫系統(tǒng)的體系結(jié)構(gòu)有關(guān)。對于關(guān)系數(shù)據(jù)庫來說,運行時的重要原則之一是確保所有的寫入操作穩(wěn)定、可靠,因而,所有的性能問題都與物理上把數(shù)據(jù)寫入磁盤的能力有關(guān)。關(guān)系數(shù)據(jù)庫力圖減少I/O操作,特別是對于讀操作,但實現(xiàn)該目標(biāo)的主要途徑只是執(zhí)行一套實現(xiàn)緩沖機制的復(fù)雜算法,而這正是數(shù)據(jù)庫層第一號性能瓶頸通??偸?CPU的主要原因。
一種替代傳統(tǒng)關(guān)系數(shù)據(jù)庫的方案是,使用在內(nèi)存中運行的數(shù)據(jù)庫(In-memory Database),例如TimesTen。內(nèi)存數(shù)據(jù)庫的出發(fā)點是允許數(shù)據(jù)臨時地寫入,但這些數(shù)據(jù)不必永久地保存到磁盤上,所有的操作都在內(nèi)存中進行。這樣,內(nèi)存數(shù)據(jù)庫不需要復(fù)雜的算法來減少I/O操作,而且可以采用比較簡單的加鎖機制,因而速度很快。
三、GUI篇
這一部分介紹的內(nèi)容適合于圖形用戶界面的應(yīng)用(Applet和普通應(yīng)用),要用到AWT或Swing。
3.1 用JAR壓縮類文件
Java檔案文件(JAR文件)是根據(jù)JavaBean標(biāo)準(zhǔn)壓縮的文件,是發(fā)布JavaBean組件的主要方式和推薦方式。JAR檔案有助于減少文件體積,縮短下載時間。例如,它有助于Applet提高啟動速度。一個JAR文件可以包含一個或者多個相關(guān)的Bean以及支持文件,比如圖形、聲音、HTML 和其他資源。
要在HTML/JSP文件中指定JAR文件,只需在Applet標(biāo)記中加入ARCHIVE = “name.jar”聲明。
請參見《使用檔案文件提高 applet 的加載速度》。
3.2 提示Applet裝入進程
你是否看到過使用Applet的網(wǎng)站,注意到在應(yīng)該運行Applet的地方出現(xiàn)了一個占位符?當(dāng)Applet的下載時間較長時,會發(fā)生什么事情?最大的可能就是用戶掉頭離去。在這種情況下,顯示一個Applet正在下載的信息無疑有助于鼓勵用戶繼續(xù)等待。
下面我們來看看一種具體的實現(xiàn)方法。首先創(chuàng)建一個很小的Applet,該Applet負責(zé)在后臺下載正式的Applet:
import java.applet.Applet;import java.applet.AppletStub;import java.awt.Label;import java.awt.Graphics;import java.awt.GridLayout;public class PreLoader extends Applet implements Runnable, AppletStub { String largeAppletName;Label label;public void init(){ // 要求裝載的正式Applet largeAppletName = getParameter(“applet”);// “請稍等”提示信息
label = new Label(“請稍等...” + largeAppletName);add(label);} public void run(){ try { // 獲得待裝載Applet的類
Class largeAppletClass = Class.forName(largeAppletName);// 創(chuàng)建待裝載Applet的實例
Applet largeApplet =(Applet)largeAppletClass.newInstance();// 設(shè)置該Applet的Stub程序 largeApplet.setStub(this);// 取消“請稍等”信息 remove(label);// 設(shè)置布局
setLayout(new GridLayout(1, 0));add(largeApplet);// 顯示正式的Applet largeApplet.init();largeApplet.start();} catch(Exception ex){ // 顯示錯誤信息
label.setText(“不能裝入指定的Applet”);} // 刷新屏幕 validate();
} public void appletResize(int width, int height){ // 把appletResize調(diào)用從stub程序傳遞到Applet resize(width, height);} }
編譯后的代碼小于2K,下載速度很快。代碼中有幾個地方值得注意。首先,PreLoader實現(xiàn)了AppletStub接口。一般地,Applet從調(diào)用者判斷自己的codebase。在本例中,我們必須調(diào)用setStub()告訴Applet到哪里提取這個信息。另一個值得注意的地方是,AppletStub接口包含許多和Applet類一樣的方法,但appletResize()方法除外。這里我們把對appletResize()方法的調(diào)用傳遞給了resize()方法。
3.3 在畫出圖形之前預(yù)先裝入它
ImageObserver接口可用來接收圖形裝入的提示信息。ImageObserver接口只有一個方法imageUpdate(),能夠用一次repaint()操作在屏幕上畫出圖形。下面提供了一個例子。public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h){ if((flags & ALLBITS)!=0 { repaint();} else if(flags &(ERROR |ABORT))!= 0){ error = true;// 文件沒有找到,考慮顯示一個占位符 repaint();} return(flags &(ALLBITS | ERROR| ABORT))== 0;}
當(dāng)圖形信息可用時,imageUpdate()方法被調(diào)用。如果需要進一步更新,該方法返回true;如果所需信息已經(jīng)得到,該方法返回false。
3.4 覆蓋update方法
update()方法的默認動作是清除屏幕,然后調(diào)用paint()方法。如果使用默認的update()方法,頻繁使用圖形的應(yīng)用可能出現(xiàn)顯示閃爍現(xiàn)象。要避免在paint()調(diào)用之前的屏幕清除操作,只需按照如下方式覆蓋update()方法:
public void update(Graphics g){ paint(g);}
更理想的方案是:覆蓋update(),只重畫屏幕上發(fā)生變化的區(qū)域,如下所示: public void update(Graphics g){ g.clipRect(x, y, w, h);paint(g);}
3.5 延遲重畫操作
對于圖形用戶界面的應(yīng)用來說,性能低下的主要原因往往可以歸結(jié)為重畫屏幕的效率低下。當(dāng)用戶改變窗口大小或者滾動一個窗口時,這一點通??梢院苊黠@地觀察到。改變窗口大小或者滾動屏幕之類的操作導(dǎo)致重畫屏幕事件大量地、快速地生成,甚至超過了相關(guān)代碼的執(zhí)行速度。對付這個問題最好的辦法是忽略所有“遲到” 的事件。
建議在這里引入一個數(shù)毫秒的時差,即如果我們立即接收到了另一個重畫事件,可以停止處理當(dāng)前事件轉(zhuǎn)而處理最后一個收到的重畫事件;否則,我們繼續(xù)進行當(dāng)前的重畫過程。
如果事件要啟動一項耗時的工作,分離出一個工作線程是一種較好的處理方式;否則,一些部件可能被“凍結(jié)”,因為每次只能處理一個事件。下面提供了一個事件處理的簡單例子,但經(jīng)過擴展后它可以用來控制工作線程。
public static void runOnce(String id, final long milliseconds){ synchronized(e_queue){ // e_queue: 所有事件的集合 if(!e_queue.containsKey(id)){ e_queue.put(token, new LastOne());} } final LastOne lastOne =(LastOne)e_queue.get(token);final long time = System.currentTimeMillis();// 獲得當(dāng)前時間 lastOne.time = time;(new Thread(){public void run(){ if(milliseconds > 0){ try {Thread.sleep(milliseconds);} // 暫停線程 catch(Exception ex){} } synchronized(lastOne.running){ // 等待上一事件結(jié)束 if(lastOne.time!= time)// 只處理最后一個事件 return;} }}).start();} private static Hashtable e_queue = new Hashtable();private static class LastOne { public long time=0;public Object running = new Object();}
3.6 使用雙緩沖區(qū)
在屏幕之外的緩沖區(qū)繪圖,完成后立即把整個圖形顯示出來。由于有兩個緩沖區(qū),所以程序可以來回切換。這樣,我們可以用一個低優(yōu)先級的線程負責(zé)畫圖,使得程序能夠利用空閑的CPU時間執(zhí)行其他任務(wù)。下面的偽代碼片斷示范了這種技術(shù)。Graphics myGraphics;Image myOffscreenImage = createImage(size().width, size().height);
Graphics offscreenGraphics = myOffscreenImage.getGraphics();offscreenGraphics.drawImage(img, 50, 50, this);myGraphics.drawImage(myOffscreenImage, 0, 0, this);
3.7 使用BufferedImage
Java JDK 1.2使用了一個軟顯示設(shè)備,使得文本在不同的平臺上看起來相似。為實現(xiàn)這個功能,Java必須直接處理構(gòu)成文字的像素。由于這種技術(shù)要在內(nèi)存中大量地進行位復(fù)制操作,早期的JDK在使用這種技術(shù)時性能不佳。為解決這個問題而提出的Java標(biāo)準(zhǔn)實現(xiàn)了一種新的圖形類型,即BufferedImage。
BufferedImage子類描述的圖形帶有一個可訪問的圖形數(shù)據(jù)緩沖區(qū)。一個BufferedImage包含一個ColorModel和一組光柵圖形數(shù)據(jù)。這個類一般使用RGB(紅、綠、藍)顏色模型,但也可以處理灰度級圖形。它的構(gòu)造函數(shù)很簡單,如下所示:
public BufferedImage(int width, int height, int imageType)
ImageType允許我們指定要緩沖的是什么類型的圖形,比如5-位RGB、8-位RGB、灰度級等。
3.8 使用VolatileImage
許多硬件平臺和它們的操作系統(tǒng)都提供基本的硬件加速支持。例如,硬件加速一般提供矩形填充功能,和利用CPU完成同一任務(wù)相比,硬件加速的效率更高。由于硬件加速分離了一部分工作,允許多個工作流并發(fā)進行,從而緩解了對CPU和系統(tǒng)總線的壓力,使得應(yīng)用能夠運行得更快。利用VolatileImage可以創(chuàng)建硬件加速的圖形以及管理圖形的內(nèi)容。由于它直接利用低層平臺的能力,性能的改善程度主要取決于系統(tǒng)使用的圖形適配器。VolatileImage的內(nèi)容隨時可能丟失,也即它是“不穩(wěn)定的(volatile)”。因此,在使用圖形之前,最好檢查一下它的內(nèi)容是否丟失。VolatileImage有兩個能夠檢查內(nèi)容是否丟失的方法: public abstract int validate(GraphicsConfiguration gc);public abstract Boolean contentsLost();
每次從VolatileImage對象復(fù)制內(nèi)容或者寫入VolatileImage時,應(yīng)該調(diào)用validate()方法。contentsLost()方法告訴我們,自從最后一次validate()調(diào)用之后,圖形的內(nèi)容是否丟失。
雖然VolatileImage是一個抽象類,但不要從它這里派生子類。VolatileImage應(yīng)該通過
Component.createVolatileImage()或者 GraphicsConfiguration.createCompatibleVolatileImage()方法創(chuàng)建。
3.9 使用Window Blitting
進行滾動操作時,所有可見的內(nèi)容一般都要重畫,從而導(dǎo)致大量不必要的重畫工作。許多操作系統(tǒng)的圖形子系統(tǒng),包括WIN32 GDI、MacOS和X/Windows,都支持Window Blitting技術(shù)。Window Blitting技術(shù)直接在屏幕緩沖區(qū)中把圖形移到新的位置,只重畫新出現(xiàn)的區(qū)域。要在Swing應(yīng)用中使用Window Blitting技術(shù),設(shè)置方法如下: setScrollMode(int mode);
在大多數(shù)應(yīng)用中,使用這種技術(shù)能夠提高滾動速度。只有在一種情形下,Window Blitting會導(dǎo)致性能降低,即應(yīng)用在后臺進行滾動操作。如果是用戶在滾動一個應(yīng)用,那么它總是在前臺,無需擔(dān)心任何負面影響。
第五篇:。NET 性能優(yōu)化方法總結(jié)與字符串連接優(yōu)化
.NET 性能優(yōu)化方法總結(jié)
Ver 1.0
2009-1-20
目錄
1.C#語言方面...............................................................................................................4
1.1 垃圾回收.......................................................................................................4
1.1.1 避免不必要的對象創(chuàng)建.....................................................................4 1.1.2 不要使用空析構(gòu)函數(shù) ★...................................................................4 1.1.3 實現(xiàn) IDisposable 接口.....................................................................4
1.2 String 操作....................................................................................................5
1.2.1 使用 StringBuilder 做字符串連接...................................................5 1.2.2 避免不必要的調(diào)用 ToUpper 或 ToLower 方法...........................5 1.2.3 最快的空串比較方法.........................................................................6
1.3 多線程...........................................................................................................6
1.3.1 線程同步.............................................................................................6 1.3.2 使用 ThreadStatic 替代 NameDataSlot ★.....................................7 1.3.3 多線程編程技巧.................................................................................7
1.4 類型系統(tǒng).......................................................................................................8
1.4.1 避免無意義的變量初始化動作.........................................................8 1.4.2 ValueType 和 ReferenceType...........................................................8 1.4.3 盡可能使用最合適的類型.................................................................9
1.5 異常處理.....................................................................................................10
1.5.1 不要吃掉異?!?..............................................................................10 1.5.2 不要吃掉異常信息★.......................................................................10 1.5.3 避免不必要的拋出異常...................................................................10 1.5.4 避免不必要的重新拋出異常...........................................................10 1.5.5 捕獲指定的異常,不要使用通用的System.Exception.................10 1.5.6 要在finally里釋放占用的資源......................................................11
1.6 反射.............................................................................................................11
1.6.1 反射分類...........................................................................................12 1.6.2 動態(tài)創(chuàng)建對象...................................................................................12 1.6.3 動態(tài)方法調(diào)用...................................................................................12 1.6.4 推薦的使用原則...............................................................................12
1.7 基本代碼技巧.............................................................................................13
1.7.1 循環(huán)寫法...........................................................................................13 1.7.2 拼裝字符串.......................................................................................13 1.7.3 避免兩次檢索集合元素...................................................................13 1.7.4 避免兩次類型轉(zhuǎn)換...........................................................................14 1.7.5為字符串容器聲明常量,不要直接把字符封裝在雙引號“ ”里面。.....................................................................................................................14 1.7.6 用StringBuilder代替使用字符串連接符 “+”...............................14 1.7.7 避免在循環(huán)體里聲明變量,...........................................................15
1.8 Hashtable......................................................................................................15 1.8.1 Hashtable機理...................................................................................15 1.8.2 使用HashTale代替其他字典集合類型的情形:......................16
1.9 避免使用ArrayList。..............................................................................16 1.10從XML對象讀取數(shù)據(jù).............................................................................17 1.11 避免使用遞歸調(diào)用和嵌套循環(huán),...........................................................17 1.12 使用適當(dāng)?shù)腃aching策略來提高性能...................................................17
2.Ado.Net....................................................................................................................17
2.1 應(yīng)用Ado.net的一些思考原則..................................................................18 2.2 Connection...................................................................................................18
2.2.1 在方法中打開和關(guān)閉連接...............................................................18 2.2.2 顯式關(guān)閉連接...................................................................................18 2.2.3 確保連接池啟用...............................................................................19 2.2.4 不要緩存連接...................................................................................19
2.3 Command.....................................................................................................19
2.3.1 使用ExecuteScalar和ExecuteNonQuery.......................................19 2.3.2 使用Prepare.....................................................................................19 2.3.3 使用綁定變量 ★.............................................................................19
2.4 DataReader...................................................................................................20
2.4.1 顯式關(guān)閉DataReader.......................................................................20 2.4.2 用索引號訪問代替名稱索引號訪問屬性.......................................20 2.4.3 使用類型化方法訪問屬性...............................................................20 2.4.4 使用多數(shù)據(jù)集...................................................................................20
2.5 DataSet.........................................................................................................21
2.5.1 利用索引加快查找行的效率...........................................................21 2.使用DataView......................................................................................21
3.ASP.NET..................................................................................................................21
3.1 減少往返行程(Reduce Round Trips)...................................................21 3.2 避免阻塞和長時間的作業(yè).........................................................................22 3.3 使用緩存.....................................................................................................22 3.4 多線程.........................................................................................................22 3.5 系統(tǒng)資源.....................................................................................................23 3.6 頁面處理.....................................................................................................23 3.7 ViewState.....................................................................................................23
4.JScript.......................................................................................................................24
4.1 JScript性能優(yōu)化的基本原則.....................................................................24 4.2 JScript語言本身的優(yōu)化.............................................................................24 4.3 DOM相關(guān)...................................................................................................27 4.4 其他.............................................................................................................28
1.C#語言方面
1.1 垃圾回收
垃圾回收解放了手工管理對象的工作,提高了程序的健壯性,但副作用就是程序代碼可能對于對象創(chuàng)建變得隨意。1.1.1 避免不必要的對象創(chuàng)建
由于垃圾回收的代價較高,所以C#程序開發(fā)要遵循的一個基本原則就是避免不必要的對象創(chuàng)建。以下列舉一些常見的情形。
1.1.1.1 避免循環(huán)創(chuàng)建對象 ★
如果對象并不會隨每次循環(huán)而改變狀態(tài),那么在循環(huán)中反復(fù)創(chuàng)建對象將帶來性能損耗。高效的做法是將對象提到循環(huán)外面創(chuàng)建。
1.1.1.2 在需要邏輯分支中創(chuàng)建對象
如果對象只在某些邏輯分支中才被用到,那么應(yīng)只在該邏輯分支中創(chuàng)建對象。
1.1.1.3 使用常量避免創(chuàng)建對象
程序中不應(yīng)出現(xiàn)如 new Decimal(0)之類的代碼,這會導(dǎo)致小對象頻繁創(chuàng)建及回收,正確的做法是使用Decimal.Zero常量。我們有設(shè)計自己的類時,也可以學(xué)習(xí)這個設(shè)計手法,應(yīng)用到類似的場景中。
1.1.1.4 使用StringBuilder做字符串連接
1.1.2 不要使用空析構(gòu)函數(shù) ★
如果類包含析構(gòu)函數(shù),由創(chuàng)建對象時會在 Finalize 隊列中添加對象的引用,以保證當(dāng)對象無法可達時,仍然可以調(diào)用到 Finalize 方法。垃圾回收器在運行期間,會啟動一個低優(yōu)先級的線程處理該隊列。相比之下,沒有析構(gòu)函數(shù)的對象就沒有這些消耗。如果析構(gòu)函數(shù)為空,這個消耗就毫無意義,只會導(dǎo)致性能降低!因此,不要使用空的析構(gòu)函數(shù)。
在實際情況中,許多曾在析構(gòu)函數(shù)中包含處理代碼,但后來因為種種原因被注釋掉或者刪除掉了,只留下一個空殼,此時應(yīng)注意把析構(gòu)函數(shù)本身注釋掉或刪除掉。1.1.3 實現(xiàn) IDisposable 接口
垃圾回收事實上只支持托管內(nèi)在的回收,對于其他的非托管資源,例如
Window GDI 句柄或數(shù)據(jù)庫連接,在析構(gòu)函數(shù)中釋放這些資源有很大問題。原因是垃圾回收依賴于內(nèi)在緊張的情況,雖然數(shù)據(jù)庫連接可能已瀕臨耗盡,但如果內(nèi)存還很充足的話,垃圾回收是不會運行的。
C#的 IDisposable 接口是一種顯式釋放資源的機制。通過提供 using 語句,還簡化了使用方式(編譯器自動生成 try...finally 塊,并在 finally 塊中調(diào)用 Dispose 方法)。對于申請非托管資源對象,應(yīng)為其實現(xiàn) IDisposable 接口,以保證資源一旦超出 using 語句范圍,即得到及時釋放。這對于構(gòu)造健壯且性能優(yōu)良的程序非常有意義!
為防止對象的 Dispose 方法不被調(diào)用的情況發(fā)生,一般還要提供析構(gòu)函數(shù),兩者調(diào)用一個處理資源釋放的公共方法。同時,Dispose 方法應(yīng)調(diào)用
System.GC.SuppressFinalize(this),告訴垃圾回收器無需再處理 Finalize 方法了。
1.2 String 操作
1.2.1 使用 StringBuilder 做字符串連接
String 是不變類,使用 + 操作連接字符串將會導(dǎo)致創(chuàng)建一個新的字符串。如果字符串連接次數(shù)不是固定的,例如在一個循環(huán)中,則應(yīng)該使用 StringBuilder 類來做字符串連接工作。因為 StringBuilder 內(nèi)部有一個 StringBuffer,連接操作不會每次分配新的字符串空間。只有當(dāng)連接后的字符串超出 Buffer 大小時,才會申請新的 Buffer 空間。典型代碼如下:
StringBuilder sb = new StringBuilder(256);for(int i = 0;i < Results.Count;i ++){
sb.Append(Results[i]);}
如果連接次數(shù)是固定的并且只有幾次,此時應(yīng)該直接用 + 號連接,保持程序簡潔易讀。實際上,編譯器已經(jīng)做了優(yōu)化,會依據(jù)加號次數(shù)調(diào)用不同參數(shù)個數(shù)的 String.Concat 方法。例如:String str = str1 + str2 + str3 + str4;
會被編譯為 String.Concat(str1, str2, str3, str4)。該方法內(nèi)部會計算總的 String 長度,僅分配一次,并不會如通常想象的那樣分配三次。作為一個經(jīng)驗值,當(dāng)字符串連接操作達到 10 次以上時,則應(yīng)該使用 StringBuilder。
這里有一個細節(jié)應(yīng)注意:StringBuilder 內(nèi)部 Buffer 的缺省值為 16,這個值實在太小。按 StringBuilder 的使用場景,Buffer 肯定得重新分配。經(jīng)驗值一般用 256 作為 Buffer 的初值。當(dāng)然,如果能計算出最終生成字符串長度的話,則應(yīng)該按這個值來設(shè)定 Buffer 的初值。使用 new StringBuilder(256)就將 Buffer 的初始長度設(shè)為了256。1.2.2 避免不必要的調(diào)用 ToUpper 或 ToLower 方法
String是不變類,調(diào)用ToUpper或ToLower方法都會導(dǎo)致創(chuàng)建一個新的字符串。如果被頻繁調(diào)用,將導(dǎo)致頻繁創(chuàng)建字符串對象。這違背了前面講到的“避免頻繁創(chuàng)建對象”這一基本原則。例如,bool.Parse方法本身已經(jīng)是忽略大小寫的,調(diào)用時不要調(diào)用ToLower方法。
另一個非常普遍的場景是字符串比較。高效的做法是使用 Compare 方法,這個方法可以做大小寫忽略的比較,并且不會創(chuàng)建新字符串。
例:
const string C_VALUE = “COMPARE”;
if(String.Compare(sVariable, C_VALUE, true)== 0)
{
Console.Write(“SAME”);
}
還有一種情況是使用 HashTable 的時候,有時候無法保證傳遞 key 的大小寫是否符合預(yù)期,往往會把 key 強制轉(zhuǎn)換到大寫或小寫方法。實際上 HashTable 有不同的構(gòu)造形式,完全支持采用忽略大小寫的 key: new HashTable(StringComparer.OrdinalIgnoreCase)。1.2.3 最快的空串比較方法
將String對象的Length屬性與0比較是最快的方法:if(str.Length == 0)
其次是與String.Empty常量或空串比較:if(str == String.Empty)或if(str == “")
注:C#在編譯時會將程序集中聲明的所有字符串常量放到保留池中(intern pool),相同常量不會重復(fù)分配。
1.3 多線程
1.3.1 線程同步
線程同步是編寫多線程程序需要首先考慮問題。C#為同步提供了 Monitor、Mutex、AutoResetEvent 和 ManualResetEvent 對象來分別包裝 Win32 的臨界區(qū)、互斥對象和事件對象這幾種基礎(chǔ)的同步機制。C#還提供了一個lock語句,方便使用,編譯器會自動生成適當(dāng)?shù)?Monitor.Enter 和 Monitor.Exit 調(diào)用。
1.3.1.1 同步粒度
同步粒度可以是整個方法,也可以是方法中某一段代碼。為方法指定 MethodImplOptions.Synchronized 屬性將標(biāo)記對整個方法同步。例如:
[MethodImpl(MethodImplOptions.Synchronized)] public static SerialManager GetInstance(){
if(instance == null){
instance = new SerialManager();}
return instance;}
通常情況下,應(yīng)減小同步的范圍,使系統(tǒng)獲得更好的性能。簡單將整個方法標(biāo)記為同步不是一個好主意,除非能確定方法中的每個代碼都需要受同步保護。
1.3.1.2 同步策略
使用 lock 進行同步,同步對象可以選擇 Type、this 或為同步目的專門構(gòu)造的成員變量。
避免鎖定Type★
鎖定Type對象會影響同一進程中所有AppDomain該類型的所有實例,這不僅可能導(dǎo)致嚴重的性能問題,還可能導(dǎo)致一些無法預(yù)期的行為。這是一個很不好的習(xí)慣。即便對于一個只包含static方法的類型,也應(yīng)額外構(gòu)造一個static的成員變量,讓此成員變量作為鎖定對象。
避免鎖定 this
鎖定 this 會影響該實例的所有方法。假設(shè)對象 obj 有 A 和 B 兩個方法,其中 A 方法使用 lock(this)對方法中的某段代碼設(shè)置同步保護?,F(xiàn)在,因為某種原因,B 方法也開始使用 lock(this)來設(shè)置同步保護了,并且可能為了完全不同的目的。這樣,A 方法就被干擾了,其行為可能無法預(yù)知。所以,作為一種良好的習(xí)慣,建議避免使用 lock(this)這種方式。
使用為同步目的專門構(gòu)造的成員變量
這是推薦的做法。方式就是 new 一個 object 對象,該對象僅僅用于同步目的。
如果有多個方法都需要同步,并且有不同的目的,那么就可以為些分別建立幾個同步成員變量。
1.3.1.4 集合同步
C#為各種集合類型提供了兩種方便的同步機制:Synchronized 包裝器和 SyncRoot 屬性。
// Creates and initializes a new ArrayList ArrayList myAL = new ArrayList();myAL.Add(” The “);myAL.Add(” quick “);myAL.Add(” brown “);myAL.Add(” fox “);
// Creates a synchronized wrapper around the ArrayList ArrayList mySyncdAL = ArrayList.Synchronized(myAL);
調(diào)用 Synchronized 方法會返回一個可保證所有操作都是線程安全的相同集合對象??紤] mySyncdAL[0] = mySyncdAL[0] + ”test“ 這一語句,讀和寫一共要用到兩個鎖。一般講,效率不高。推薦使用 SyncRoot 屬性,可以做比較精細的控制。
1.3.2 使用 ThreadStatic 替代 NameDataSlot ★ 存取 NameDataSlot 的 Thread.GetData 和 Thread.SetData 方法需要線程同步,涉及兩個鎖:一個是 LocalDataStore.SetData 方法需要在 AppDomain 一級加鎖,另一個是 ThreadNative.GetDomainLocalStore 方法需要在 Process 一級加鎖。如果一些底層的基礎(chǔ)服務(wù)使用了 NameDataSlot,將導(dǎo)致系統(tǒng)出現(xiàn)嚴重的伸縮性問題。
規(guī)避這個問題的方法是使用 ThreadStatic 變量。示例如下:
public sealed class InvokeContext {
[ThreadStatic]
private static InvokeContext current;
private Hashtable maps = new Hashtable();}
1.3.3 多線程編程技巧
1.3.3.1 使用 Double Check 技術(shù)創(chuàng)建對象
internal IDictionary KeyTable { get {
if(this._keyTable == null){
lock(base._lock){
if(this._keyTable == null){
this._keyTable = new Hashtable();} } }
return this._keyTable;} }
創(chuàng)建單例對象是很常見的一種編程情況。一般在 lock 語句后就會直接創(chuàng)建對象了,但這不夠安全。因為在 lock 鎖定對象之前,可能已經(jīng)有多個線程進入到了第一個 if 語句中。如果不加第二個 if 語句,則單例對象會被重復(fù)創(chuàng)建,新的實例替代掉舊的實例。如果單例對象中已有數(shù)據(jù)不允許被破壞或者別的什么原因,則應(yīng)考慮使用 Double Check 技術(shù)。
1.4 類型系統(tǒng) 1.4.1 避免無意義的變量初始化動作
CLR保證所有對象在訪問前已初始化,其做法是將分配的內(nèi)存清零。因此,不需要將變量重新初始化為0、false或null。
需要注意的是:方法中的局部變量不是從堆而是從棧上分配,所以C#不會做清零工作。如果使用了未賦值的局部變量,編譯期間即會報警。不要因為有這個印象而對所有類的成員變量也做賦值動作,兩者的機理完全不同!1.4.2 ValueType 和 ReferenceType
1.4.2.1 以引用方式傳遞值類型參數(shù)
值類型從調(diào)用棧分配,引用類型從托管堆分配。當(dāng)值類型用作方法參數(shù)時,默認會進行參數(shù)值復(fù)制,這抵消了值類型分配效率上的優(yōu)勢。作為一項基本技巧,以引用方式傳遞值類型參數(shù)可以提高性能。
1.4.2.2 為 ValueType 提供 Equals 方法
.net 默認實現(xiàn)的 ValueType.Equals 方法使用了反射技術(shù),依靠反射來獲得所有成員變量值做比較,這個效率極低。如果我們編寫的值對象其 Equals 方法要被用到(例如將值對象放到 HashTable 中),那么就應(yīng)該重載 Equals 方法。
public struct Rectangle {
public double Length;public double Breadth;
public override bool Equals(object ob){
if(ob is Rectangle)
return Equels((Rectangle)ob))else
return false;}
private bool Equals(Rectangle rect){
return this.Length == rect.Length && this.Breadth == rect.Breach;} }
1.4.2.3 避免裝箱和拆箱
C#可以在值類型和引用類型之間自動轉(zhuǎn)換,方法是裝箱和拆箱。裝箱需要從堆上分配對象并拷貝值,有一定性能消耗。如果這一過程發(fā)生在循環(huán)中或是作為底層方法被頻繁調(diào)用,則應(yīng)該警惕累計的效應(yīng)。
一種經(jīng)常的情形出現(xiàn)在使用集合類型時。例如:
ArrayList al = new ArrayList();for(int i = 0;i < 1000;i ++){ al.Add(i);// Implicitly boxed because Add()takes an object }
int f =(int)al[ 0 ];// The element is unboxed
但是得當(dāng)心!如果你像使用引用類型那么頻繁的使用一個值類型的話,值類型的優(yōu)勢會很快被耗盡。比如,把一個值類型壓到一個含有對象類型的群集。這叫做裝箱,很耗用處理器周期,尤其是當(dāng)你的代碼在把它作為值(對它進行數(shù)學(xué)運算)和把它作為引用之間來回運行時。
1.4.3 盡可能使用最合適的類型
? 盡可能使用最合適的類型來描述數(shù)據(jù),從而減少類型轉(zhuǎn)換。
? 使用泛型來創(chuàng)建群集和其它的數(shù)據(jù)結(jié)構(gòu),這樣,在運行時,它們就可以被實例化來存儲剛好合適的類型。這節(jié)省了裝箱/拆箱和類型轉(zhuǎn)換的時間。
? 在C#中使用as,而不是is。關(guān)鍵字is用來查看引用是否可以被作為某個具體的類型,但是并不返回轉(zhuǎn)換到這個類型的引用。所以,通常當(dāng)你從is獲得一個正的結(jié)果時,你首先應(yīng)該cast——有效地執(zhí)行兩次cast。采用as關(guān)鍵詞時,如果可用,則返回cast為新類型的引用;否則返回null。你可以查看null然后做你喜歡做的事情。整體來說,As方法要比is方法快50%。
1.5 異常處理
異常也是現(xiàn)代語言的典型特征。與傳統(tǒng)檢查錯誤碼的方式相比,異常是強制性的(不依賴于是否忘記了編寫檢查錯誤碼的代碼)、強類型的、并帶有豐富的異常信息(例如調(diào)用棧)。1.5.1 不要吃掉異常★
關(guān)于異常處理的最重要原則就是:不要吃掉異常。這個問題與性能無關(guān),但對于編寫健壯和易于排錯的程序非常重要。這個原則換一種說法,就是不要捕獲那些你不能處理的異常。
吃掉異常是極不好的習(xí)慣,因為你消除了解決問題的線索。一旦出現(xiàn)錯誤,定位問題將非常困難。除了這種完全吃掉異常的方式外,只將異常信息寫入日志文件但并不做更多處理的做法也同樣不妥。1.5.2 不要吃掉異常信息★
有些代碼雖然拋出了異常,但卻把異常信息吃掉了。
為異常披露詳盡的信息是程序員的職責(zé)所在。如果不能在保留原始異常信息含義的前提下附加更豐富和更人性化的內(nèi)容,那么讓原始的異常信息直接展示也要強得多。千萬不要吃掉異常。1.5.3 避免不必要的拋出異常 拋出異常和捕獲異常屬于消耗比較大的操作,在可能的情況下,應(yīng)通過完善程序邏輯避免拋出不必要不必要的異常。與此相關(guān)的一個傾向是利用異常來控制處理邏輯。盡管對于極少數(shù)的情況,這可能獲得更為優(yōu)雅的解決方案,但通常而言應(yīng)該避免。1.5.4 避免不必要的重新拋出異常
如果是為了包裝異常的目的(即加入更多信息后包裝成新異常),那么是合理的。但是有不少代碼,捕獲異常沒有做任何處理就再次拋出,這將無謂地增加一次捕獲異常和拋出異常的消耗,對性能有傷害。
1.5.5 捕獲指定的異常,不要使用通用的System.Exception.//避免
try
{
}
catch(Exception exc)
{
}
//推薦
try
{
}
catch(System.NullReferenceException exc)
{
}
catch(System.ArgumentOutOfRangeException exc)
{
}
catch(System.InvalidCastException exc)
{
}
1.5.6 要在finally里釋放占用的資源
使用Try...catch...finally時,要在finally里釋放占用的資源如連接,文件流等,不然在Catch到錯誤后占用的資源不能釋放。
try
{
...}
catch
{...}
finally
{
conntion.close()
}
1.6 反射
反射是一項很基礎(chǔ)的技術(shù),它將編譯期間的靜態(tài)綁定轉(zhuǎn)換為延遲到運行期間的動態(tài)綁定。在很多場景下(特別是類框架的設(shè)計),可以獲得靈活易于擴展的架構(gòu)。但帶來的問題是與靜態(tài)綁定相比,動態(tài)綁定會對性能造成較大的傷害。1.6.1 反射分類
type comparison :類型判斷,主要包括 is 和 typeof 兩個操作符及對象實例上的 GetType 調(diào)用。這是最輕型的消耗,可以無需考慮優(yōu)化問題。注意 typeof 運算符比對象實例上的 GetType 方法要快,只要可能則優(yōu)先使用 typeof 運算符。
member enumeration : 成員枚舉,用于訪問反射相關(guān)的元數(shù)據(jù)信息,例如Assembly.GetModule、Module.GetType、Type對象上的IsInterface、IsPublic、GetMethod、GetMethods、GetProperty、GetProperties、GetConstructor調(diào)用等。盡管元數(shù)據(jù)都會被CLR緩存,但部分方法的調(diào)用消耗仍非常大,不過這類方法調(diào)用頻度不會很高,所以總體看性能損失程度中等。
member invocation:成員調(diào)用,包括動態(tài)創(chuàng)建對象及動態(tài)調(diào)用對象方法,主要有Activator.CreateInstance、Type.InvokeMember等。1.6.2 動態(tài)創(chuàng)建對象
C#主要支持 5 種動態(tài)創(chuàng)建對象的方式:
1.Type.InvokeMember
2.ContructorInfo.Invoke
3.Activator.CreateInstance(Type)
4.Activator.CreateInstance(assemblyName, typeName)
5.Assembly.CreateInstance(typeName)
最快的是方式 3,與 Direct Create 的差異在一個數(shù)量級之內(nèi),約慢 7 倍的水平。其他方式,至少在 40 倍以上,最慢的是方式 4,要慢三個數(shù)量級。1.6.3 動態(tài)方法調(diào)用
方法調(diào)用分為編譯期的早期綁定和運行期的動態(tài)綁定兩種,稱為Early-Bound Invocation和Late-Bound Invocation。Early-Bound Invocation可細分為Direct-call、Interface-call和Delegate-call。Late-Bound Invocation主要有Type.InvokeMember和MethodBase.Invoke,還可以通過使用LCG(Lightweight Code Generation)技術(shù)生成IL代碼來實現(xiàn)動態(tài)調(diào)用。
從測試結(jié)果看,相比Direct Call,Type.InvokeMember要接近慢三個數(shù)量級;MethodBase.Invoke雖然比Type.InvokeMember要快三倍,但比Direct Call仍慢270倍左右??梢妱討B(tài)方法調(diào)用的性能是非常低下的。我們的建議是:除非要滿足特定的需求,否則不要使用!1.6.4 推薦的使用原則
模式
1. 如果可能,則避免使用反射和動態(tài)綁定
2. 使用接口調(diào)用方式將動態(tài)綁定改造為早期綁定
3. 使用Activator.CreateInstance(Type)方式動態(tài)創(chuàng)建對象
4. 使用typeof操作符代替GetType調(diào)用
反模式
1. 在已獲得Type的情況下,卻使用Assembly.CreateInstance(type.FullName)
1.7 基本代碼技巧
這里描述一些應(yīng)用場景下,可以提高性能的基本代碼技巧。對處于關(guān)鍵路徑的代碼,進行這類的優(yōu)化還是很有意義的。普通代碼可以不做要求,但養(yǎng)成一種好的習(xí)慣也是有意義的。1.7.1 循環(huán)寫法
可以把循環(huán)的判斷條件用局部變量記錄下來。局部變量往往被編譯器優(yōu)化為直接使用寄存器,相對于普通從堆或棧中分配的變量速度快。如果訪問的是復(fù)雜計算屬性的話,提升效果將更明顯。for(int i = 0, j = collection.GetIndexOf(item);i < j;i++)
需要說明的是:這種寫法對于CLR集合類的Count屬性沒有意義,原因是編譯器已經(jīng)按這種方式做了特別的優(yōu)化。1.7.2 拼裝字符串
拼裝好之后再刪除是很低效的寫法。有些方法其循環(huán)長度在大部分情況下為1,這種寫法的低效就更為明顯了:
public static string ToString(MetadataKey entityKey){
string str = ”“;
object [] vals = entityKey.values;for(int i = 0;i < vals.Length;i ++){
str += ” , “ + vals[i].ToString();}
return str == ”“ ? ”“ : str.Remove(0 , 1);}
推薦下面的寫法:
if(str.Length == 0)str = vals[i].ToString();else
str += ” , “ + vals[i].ToString();其實這種寫法非常自然,而且效率很高,完全不需要用個Remove方法繞來繞去。1.7.3 避免兩次檢索集合元素
獲取集合元素時,有時需要檢查元素是否存在。通常的做法是先調(diào)用ContainsKey(或Contains)方法,然后再獲取集合元素。這種寫法非常符合邏輯。
但如果考慮效率,可以先直接獲取對象,然后判斷對象是否為null來確定元素是否存在。對于Hashtable,這可以節(jié)省一次GetHashCode調(diào)用和n次Equals比較。
如下面的示例:
public IData GetItemByID(Guid id){
IData data1 = null;
if(this.idTable.ContainsKey(id.ToString()){
data1 = this.idTable[id.ToString()] as IData;}
return data1;}
其實完全可用一行代碼完成:return this.idTable[id] as IData;1.7.4 避免兩次類型轉(zhuǎn)換
考慮如下示例,其中包含了兩處類型轉(zhuǎn)換:
if(obj is SomeType){
SomeType st =(SomeType)obj;st.SomeTypeMethod();}
效率更高的做法如下:
SomeType st = obj as SomeType;if(st!= null){
st.SomeTypeMethod();}
1.7.5為字符串容器聲明常量,不要直接把字符封裝在雙引號” “里面。
//避免
//
MyObject obj = new MyObject();
obj.Status = ”ACTIVE“;
//推薦
const string C_STATUS = ”ACTIVE“;
MyObject obj = new MyObject();
obj.Status = C_STATUS;
1.7.6 用StringBuilder代替使用字符串連接符
//避免
String sXML = ” “;
sXML += ”“;
sXML += ”Data“;
sXML += ”“;
sXML += ”“;
//推薦
StringBuilder sbXML = new StringBuilder();
sbXML.Append(” “);
sbXML.Append(”“);
sbXML.Append(”Data“);
sbXML.Append(”“);
sbXML.Append(”“);
1.7.7 避免在循環(huán)體里聲明變量,應(yīng)該在循環(huán)體外聲明變量,在循環(huán)體里初始化。
//避免
for(int i=0;i<10;i++)
+”
“
{
SomeClass objSC = new SomeClass();} //推薦
SomeClass objSC = null;for(int i=0;i<10;i++){
objSC = new SomeClass();)
1.8 Hashtable 1.8.1 Hashtable機理
Hashtable是一種使用非常頻繁的基礎(chǔ)集合類型。需要理解影響Hashtable的效率有兩個因素:一是散列碼(GetHashCode方法),二是等值比較(Equals方法)。Hashtable首先使用鍵的散列碼將對象分布到不同的存儲桶中,隨后在該特定的存儲桶中使用鍵的Equals方法進行查找。
良好的散列碼是第一位的因素,最理想的情況是每個不同的鍵都有不同的散列碼。Equals方法也很重要,因為散列只需要做一次,而存儲桶中查找鍵可能需要做多次。從實際經(jīng)驗看,使用Hashtable時,Equals方法的消耗一般會占到一半以上。
System.Object類提供了默認的GetHashCode實現(xiàn),使用對象在內(nèi)存中的地址作為散列碼。我們遇到過一個用Hashtable來緩存對象的例子,每次根據(jù)傳遞的OQL表達式構(gòu)造出一個ExpressionList對象,再調(diào)用QueryCompiler的方法編譯得到CompiledQuery對象。以ExpressionList對象和CompiledQuery對象作為鍵值對存儲到Hashtable中。ExpressionList對象沒有重載GetHashCode實現(xiàn),其超類ArrayList也沒有,這樣最后用的就是System.Object類的GetHashCode實現(xiàn)。由于ExpressionList對象會每次構(gòu)造,因此它的HashCode每次都不同,所以這個CompiledQueryCache根本就沒有起到預(yù)想的作用。這個小小的疏漏帶來了重大的性能問題,由于解析OQL表達式頻繁發(fā)生,導(dǎo)致CompiledQueryCache不斷增長,造成服務(wù)器內(nèi)存泄漏!解決這個問題的最簡單方法就是提供一個常量實現(xiàn),例如讓散列碼為常量0。雖然這會導(dǎo)致所有對象匯聚到同一個存儲桶中,效率不高,但至少可以解決掉內(nèi)存泄漏問題。當(dāng)然,最終還是會實現(xiàn)一個高效的GetHashCode方法的。
以上介紹這些Hashtable機理,主要是希望大家理解:如果使用Hashtable,你應(yīng)該檢查一下對象是否提供了適當(dāng)?shù)腉etHashCode和Equals方法實現(xiàn)。否則,有可能出現(xiàn)效率不高或者與預(yù)期行為不符的情況。
1.8.2 使用HashTale代替其他字典集合類型的情形:
其他字典集合類型(如StringDictionary,NameValueCollection,HybridCollection),存放少量數(shù)據(jù)的時候可以使用HashTable。很多非泛型集合類都有對應(yīng)的泛型集合類,下面是常用的非泛型集合類以及對應(yīng)的泛型集合類:
非泛型集合類 泛型集合類
ArrayList List
HashTable DIctionary
SortedList SortedList
我們用的比較多的非泛型集合類主要有 ArrayList類 和 HashTable類。我們經(jīng)常用HashTable 來存儲將要寫入到數(shù)據(jù)庫或者返回的信息,在這之間要不斷的進行類型的轉(zhuǎn)化,增加了系統(tǒng)裝箱和拆箱的負擔(dān),如果我們操縱的數(shù)據(jù)類型相對確定的化
用 Dictionary
1.9 避免使用ArrayList。
因為任何對象添加到ArrayList都要封箱為System.Object類型,從ArrayList取出數(shù)據(jù)時,要拆箱回實際的類型。建議使用自定義的集合類型代替ArrayList。.net 2.0提供了一個新的類型,叫泛型,這是一個強類型,使用泛型集合就可以避免了封箱和拆箱的發(fā)生,提高了性能。
1.10從XML對象讀取數(shù)據(jù)
如果只是從XML對象讀取數(shù)據(jù),用只讀的XPathDocument代替XMLDocument,可以提高性能
//避免
XmlDocument xmld = new XmlDocument();
xmld.LoadXml(sXML);
txtName.Text = xmld.SelectSingleNode(”/packet/child“).InnerText;
.//推薦
XPathDocument xmldContext = new XPathDocument(new StringReader(oContext.Value));
XPathNavigator xnav = xmldContext.CreateNavigator();
XPathNodeIterator xpNodeIter = xnav.Select(”packet/child“);
iCount = xpNodeIter.Count;
xpNodeIter = xnav.SelectDescendants(XPathNodeType.Element, false);
while(xpNodeIter.MoveNext())
{
sCurrValues += xpNodeIter.Current.Value+”~“;
}
}
1.11 避免使用遞歸調(diào)用和嵌套循環(huán),使用他們會嚴重影響性能,在不得不用的時候才使用。
1.12 使用適當(dāng)?shù)腃aching策略來提高性能
2.Ado.Net
2.1 應(yīng)用Ado.net的一些思考原則
1.根據(jù)數(shù)據(jù)使用的方式來設(shè)計數(shù)據(jù)訪問層 2.緩存數(shù)據(jù),避免不必要的操作 3.使用服務(wù)帳戶進行連接 4.必要時申請,盡早釋放 5.關(guān)閉可關(guān)閉的資源 6.減少往返
7.僅返回需要的數(shù)據(jù) 8.選擇適當(dāng)?shù)氖聞?wù)類型 9.使用存儲過程
2.2 Connection 數(shù)據(jù)庫連接是一種共享資源,并且打開和關(guān)閉的開銷較大。Ado.net默認啟用了連接池機制,關(guān)閉連接不會真的關(guān)閉物理連接,而只是把連接放回到連接池中。因為池中共享的連接資源始終是有限的,如果在使用連接后不盡快關(guān)閉連接,那么就有可能導(dǎo)致申請連接的線程被阻塞住,影響整個系統(tǒng)的性能表現(xiàn)。2.2.1 在方法中打開和關(guān)閉連接
這個原則有幾層含義:
1.主要目的是為了做到必要時申請和盡早釋放
2.不要在類的構(gòu)造函數(shù)中打開連接、在析構(gòu)函數(shù)中釋放連接。因為這將依賴于垃圾回收,而垃圾回收只受內(nèi)存影響,回收時機不定
3.不要在方法之間傳遞連接,這往往導(dǎo)致連接保持打開的時間過長
這里強調(diào)一下在方法之間傳遞連接的危害:曾經(jīng)在壓力測試中遇到過一個測試案例,當(dāng)增大用戶數(shù)的時候,這個案例要比別的案例早很久就用掉連接池中的所有連接。經(jīng)分析,就是因為A方法把一個打開的連接傳遞到了B方法,而B方法又調(diào)用了一個自行打開和關(guān)閉連接的C方法。在A方法的整個運行期間,它至少需要占用兩條連接才能夠成功工作,并且其中的一條連接占用時間還特別長,所以造成連接池資源緊張,影響了整個系統(tǒng)的可伸縮性!
2.2.2 顯式關(guān)閉連接
Connection對象本身在垃圾回收時可以被關(guān)閉,而依賴垃圾回收是很不好的策略。推薦使用using語句顯式關(guān)閉連接,如下例:
using(SqlConnection conn = new SqlConnection(connString)){
conn.Open();
} // Dispose is automatically called on the conn variable here
2.2.3 確保連接池啟用
Ado.net是為每個不同的連接串建立連接池,因此應(yīng)該確保連接串不會出現(xiàn)與具體用戶相關(guān)的信息。另外,要注意連接串是大小寫敏感的。2.2.4 不要緩存連接
例如,把連接緩存到Session或Application中。在啟用連接池的情況下,這種做法沒有任何意義。
2.3 Command
2.3.1 使用ExecuteScalar和ExecuteNonQuery
如果想返回像Count(*)、Sum(Price)或Avg(Quantity)那樣的單值,可以使用ExecuteScalar方法。ExecuteScalar返回第一行第一列的值,將結(jié)果集作為標(biāo)量值返回。因為單獨一步就能完成,所以ExecuteScalar不僅簡化了代碼,還提高了性能。
使用不返回行的SQL語句時,例如修改數(shù)據(jù)(INSERT、UPDATE或DELETE)或僅返回輸出參數(shù)或返回值,請使用ExecuteNonQuery。這避免了用于創(chuàng)建空DataReader的任何不必要處理。2.3.2 使用Prepare
當(dāng)需要重復(fù)執(zhí)行同一SQL語句多次,可考慮使用Prepare方法提升效率。需要注意的是,如果只是執(zhí)行一次或兩次,則完全沒有必要。例如:
cmd.CommandText = ”insert into Table1(Col1, Col2)values(@val1, @val2)“;
cmd.Parameters.Add(”@val1“, SqlDbType.Int, 4, ”Col1“);cms.Parameters.Add(”@val2“, SqlDbType.NChar, 50, ”Col2“);
cmd.Parameters[0].Value = 1;
cmd.Parameters[1].Value = ”XXX“;cmd.Prepare();
cmd.ExecuteNonQuery();
cmd.Parameters[0].Value = 2;
cmd.Parameters[1].Value = ”YYY“;cmd.ExecuteNonQuery();
cmd.Parameters[0].Value = 3;
cmd.Parameters[1].Value = ”ZZZ“;cmd.ExecuteNonQuery();
2.3.3 使用綁定變量 ★
SQL語句需要先被編譯成執(zhí)行計劃,然后再執(zhí)行。如果使用綁定變量的方式,那么這個執(zhí)行計劃就可以被后續(xù)執(zhí)行的SQL語句所復(fù)用。而如果直接把參數(shù)合并到了SQL語句中,由于參數(shù)值千變?nèi)f化,執(zhí)行計劃就難以被復(fù)用了。例如上面Prepare一節(jié)給出的示例,如果把參數(shù)值直接寫到insert語句中,那么上面的四次調(diào)用將需要編譯四次執(zhí)行計劃。
為避免這種情況造成性能損失,要求一律使用綁定變量方式。
2.4 DataReader
DataReader最適合于訪問只讀的單向數(shù)據(jù)集。與DataSet不同,數(shù)據(jù)集并不全部在內(nèi)存中,而是隨不斷發(fā)出的read請求,一旦發(fā)現(xiàn)數(shù)據(jù)緩沖區(qū)中的數(shù)據(jù)均被讀取,則從數(shù)據(jù)源傳輸一個數(shù)據(jù)緩沖區(qū)大小的數(shù)據(jù)塊過來。另外,DataReader保持連接,DataSet則與連接斷開。2.4.1 顯式關(guān)閉DataReader
與連接類似,也需要顯式關(guān)閉DataReader。另外,如果與DataReader關(guān)聯(lián)的Connection僅為DataReader服務(wù)的話,可考慮使用Command對象的ExecuteReader(CommandBehavior.CloseConnection)方式。這可以保證當(dāng)DataReader關(guān)閉時,同時自動關(guān)閉Connection。2.4.2 用索引號訪問代替名稱索引號訪問屬性
從Row中訪問某列屬性,使用索引號的方式比使用名稱方式有細微提高。如果會被頻繁調(diào)用,例如在循環(huán)中,那么可考慮此類優(yōu)化。示例如下:
cmd.CommandText = ”select Col1, Col2 from Table1“;SqlDataReader dr = cmd.ExecuteReader();
int col1 = dr.GetOrdinal(”Col1“);int col2 = dr.GetOrdinal(”Col2“);
while(dr.Read()){
Console.WriteLine(dr[col1] + ”_“ + dr[col2]);}
2.4.3 使用類型化方法訪問屬性
從Row中訪問某列屬性,用GetString、GetInt32這種顯式指明類型的方法,其效率較通用的GetValue方法有細微提高,因為不需要做類型轉(zhuǎn)換。2.4.4 使用多數(shù)據(jù)集
部分場景可以考慮一次返回多數(shù)據(jù)集來降低網(wǎng)絡(luò)交互次數(shù),提升效率。示例如下:
cmd.CommandText = ”StoredProcedureName“;// The stored procedure returns multiple result sets.SqlDataReader dr = cmd.ExecuteReader();
while(dr.read())// read first result set
dr.NextResult();
while(dr.read())//
2.5 DataSet
2.5.1 利用索引加快查找行的效率
如果需要反復(fù)查找行,建議增加索引。有兩種方式:
1.設(shè)置DataTable的PrimaryKey
適用于按PrimaryKey查找行的情況。注意此時應(yīng)調(diào)用DataTable.Rows.Find方法,一般慣用的Select方法不能利用索引。2.使用DataView
適用于按Non-PrimaryKey查找行的情況??蔀镈ataTable創(chuàng)建一個DataView,并通過SortOrder參數(shù)指示建立索引。此后使用Find或FindRows查找行。
3.ASP.NET
3.1 減少往返行程(Reduce Round Trips)
使用下面的方法可以減少Web服務(wù)器和Browser之間的往返行程:
1.為Browser啟用緩存
如果呈現(xiàn)的內(nèi)容是靜態(tài)的或變化周期較長,應(yīng)啟用Browser緩存,避免發(fā)出冗余的http請求。2.緩沖頁面輸出
如果可能,則盡量緩沖頁面輸出,處理結(jié)束后再一次傳送到客戶端,這可以避免頻繁傳遞小塊內(nèi)容所造成的多次網(wǎng)絡(luò)交互。由于這種方式在頁面處理結(jié)束之前客戶端無法看到頁面內(nèi)容,因此如果一個頁面的尺寸較大的話,可考慮使用Response.Flush方法。該方法強制輸出迄今為止在緩沖區(qū)中的內(nèi)容,你應(yīng)當(dāng)采用合理的算法控制調(diào)用Response.Flush方法的次數(shù)。
3.使用Server.Transfer重定向請求
使用Server.Transfer方法重定向請求優(yōu)于Response.Redirect方法。原因是Response.Redirect會向Broswer回送一個響應(yīng)頭,在響應(yīng)頭中指出重定向的URL,之后Brower使用新的URL重新發(fā)出請求。而Server.Transfer方法直接是一個簡單的服務(wù)端調(diào)用,完全沒有這些開銷!
需要注意Server.Transfer有局限性:第一,它會跳過安全檢查;第二,只適用于在同一Web應(yīng)用內(nèi)的頁面間跳轉(zhuǎn)。
3.2 避免阻塞和長時間的作業(yè) 如果需要運行阻塞或長時間運行的操作,可以考慮使用異步調(diào)用的機制,以便Web服務(wù)器能夠繼續(xù)處理其它的請求。
1.使用異步方式調(diào)用Web服務(wù)和遠程對象
只要有可能就要避免在請求的處理過程中對Web服務(wù)和遠程對象的同步調(diào)用,因為它占用的是的ASP.NET 線程池中的工作線程,這將直接影響Web服務(wù)器響應(yīng)其它請求的能力。
2.考慮給不需要返回值的Web方法或遠程對象的方法添加OneWay屬性
這種模式能讓W(xué)eb Server調(diào)用之后就立即返回??筛鶕?jù)實際情況決定是否使用這種方法。
3.使用工作隊列
將作業(yè)提交到服務(wù)器上的工作隊列中??蛻舳送ㄟ^發(fā)送請求來輪詢作業(yè)的執(zhí)行結(jié)果。
3.3 使用緩存
緩存能在很大程度上決定ASP.NET應(yīng)用的最終性能。Asp.net支持頁面輸出緩存和頁面部分緩存,并提供Cache API,供應(yīng)用程序緩存自己的數(shù)據(jù)。是否使用緩存可考慮下面的要點:
1.識別創(chuàng)建與訪問代價較大的數(shù)據(jù)
2.評估需要緩存數(shù)據(jù)的易變性
3.評估數(shù)據(jù)的使用頻次
4.將要緩存數(shù)據(jù)中易變數(shù)據(jù)和不變數(shù)據(jù)分離,只緩存不變數(shù)據(jù)
5.選擇合適的緩存機制(除Asp.net Cache外,Application state和Session state也可以作為緩存使用)
3.4 多線程
1.避免在請求處理過程中創(chuàng)建線程
在執(zhí)行請求的過程中創(chuàng)建線程是一種代價較大的操作,會嚴重影響Web Server的性能。如果后續(xù)的操作必須用線程完成,建議通過thread pool來創(chuàng)建/管理線程。
2.不要依賴線程數(shù)據(jù)槽或線程靜態(tài)變量
由于執(zhí)行請求的線程是ASP.NET thread pool中的工作線程,同一個Client的兩次請求不一定由相同的線程來處理。
3.避免阻塞處理請求的線程
參考”避免阻塞和長時間的作業(yè)“小節(jié)。
4.避免異步調(diào)用
這和1的情況類似。異步調(diào)用會導(dǎo)致創(chuàng)建新的線程,增加服務(wù)器的負擔(dān)。所以,如果沒有并發(fā)的作業(yè)要執(zhí)行,就不要執(zhí)行異步調(diào)用。
3.5 系統(tǒng)資源
1.考慮實現(xiàn)資源池以提升性能
2.明確地調(diào)用Dispose或Close釋放系統(tǒng)資源 3.不要緩存或長時間占用資源池中的資源 4.盡可能晚的申請,盡可能早的釋放
3.6 頁面處理
1.盡量減小Page的尺寸
包括縮短控件的名稱、CSS的class的名稱、去掉無謂空行和空格、禁用不需要的ViewState
2.啟用頁面輸出的緩沖區(qū)(Buffer)
如果Buffer的機制被關(guān)閉,可以用下面的方法打開。
使用程序打開頁面輸出緩存:
Response.BufferOutput = true;
使用@Page開關(guān)打開頁面輸出緩沖機制:
<%@ Page Buffer = ”true“ %>
使用Web.config或Machine.config配置文件的
節(jié)點:
3.利用Page.IsPostBack優(yōu)化頁面輸出
4.通過分離頁面的不同的內(nèi)容,來提高緩存效率和減少呈現(xiàn)的時間
5.優(yōu)化復(fù)雜和代價較大的循環(huán)
6.合理利用客戶端的計算資源,將一些操作轉(zhuǎn)移到客戶端進行
3.7 ViewState
ViewState是Asp.net為服務(wù)端控件在頁面回傳之間跟蹤狀態(tài)信息而設(shè)計的一種機制。
1.關(guān)閉ViewState
如果不需要跟蹤頁面狀態(tài),例如頁面不會 回傳(PostBack)、不需要處理服務(wù)端控件事件或者每次頁面刷新時都會重新計算控件內(nèi)容,那么就不需要用ViewState來記錄頁面狀態(tài)了??梢詫μ囟ǖ腤ebControl設(shè)置EnableViewState屬性,也可以在頁面一級設(shè)置:
<%@ Page EnableViewState=”false“ %>
2.在恰當(dāng)?shù)臅r間點初始化控件屬性
ASP.NET的控件在執(zhí)行構(gòu)造函數(shù)、初始化的期間設(shè)置的屬性不會被跟蹤變化;而在初始化階段之后對屬性的修改都會被跟蹤,并最終記錄到IE頁面的__VIEWSTATE之中。所以,選擇合理的初始化控件屬性的執(zhí)行點,能有效的減小頁面尺寸。
3.謹慎選擇放到ViewState中的內(nèi)容
放到ViewState中的內(nèi)容會被序列化/反序列化,Asp.net為String、Integer、Boolean等基本類型的序列化做了優(yōu)化,如果Array、ArrayList、HashTable存儲的是基本類型效率也較高,但其它類型則需要提供類型轉(zhuǎn)換器(Type Converter),否則將使用代價昂貴的二進制序列化程序。
4.JScript
4.1 JScript性能優(yōu)化的基本原則
1.盡可能少地減少執(zhí)行次數(shù)。畢竟對解釋語言來說,每一個執(zhí)行步驟,都需要和解釋引擎做一次交互。
2.盡可能使用語言內(nèi)置的功能,比如串鏈接。
3.盡可能使用系統(tǒng)提供的API來進行優(yōu)化。因為這些API是編譯好的二進制代碼,執(zhí)行效率很高。
4.書寫最正確的代碼。容錯功能是要付出性能代價的。
4.2 JScript語言本身的優(yōu)化
4.2.1 變量
1.盡量使用局部變量。
因為全局變量其實是全局對象的成員,而局部變量在棧上定義,優(yōu)先查找,性能相對于全局變量要高。
2.盡量在一個語句中做定義變量和賦值。
3.省略不必要的變量定義。
如果變量的定義可以被一個常量替代,就直接使用常量。
4.使用Object語法對對象賦值。Object的賦值語法在操作復(fù)雜對象時效率更高。
例如,可以將下面的代碼:
car = new Object();car.make = ”Honda“;car.model = ”Civic“;
car.transmission = ”manual“;car.miles = 100000;
car.condition = ”needs work“;替換成:
car = {
make: ”Honda“, model: ”Civic“,transmission: ”manual“, miles: 100000,condition: ”needs work“ }
4.2.2 對象緩存
1.緩存對象查找的中間結(jié)果。
因為JavaScript的解釋性,所以a.b.c.d.e,需要進行至少4次查詢操作,先檢查a再檢查a中的b,再檢查b中的c,如此往下。所以如果這樣的表達式重復(fù)出現(xiàn),只要可能,應(yīng)該盡量少出現(xiàn)這樣的表達式,可以利用局部變量,把它放入一個臨時的地方進行查詢。
2.緩存創(chuàng)建時間較長的對象。
自定義高級對象和Date、RegExp對象在構(gòu)造時都會消耗大量時間。如果可以復(fù)用,應(yīng)采用緩存的方式。
4.2.3 字符串操作
1.使用”+=“ 追加字符串,使用”+“來連接字符串。
如果是追加字符串,最好使用s+=anotherStr操作,而不是要使用s=s+anotherStr。
如果要連接多個字符串,應(yīng)該使用”+“,如:
s+=a;
s+=b;
s+=c;
應(yīng)該寫成
s+=a + b + c;
2.連接大量的字符串,應(yīng)使用Array的join方法。如果是收集字符串,最好使用JavaScript數(shù)組緩存,最后使用join方法連接起來,如下:
var buf = new Array();for(var i = 0;i < 100;i++){
buf.push(i.toString());}
var all = buf.join(”“);
4.2.4 類型轉(zhuǎn)換
1.使用Math.floor()或者Math.round()將浮點數(shù)轉(zhuǎn)換成整型。
浮點數(shù)轉(zhuǎn)換成整型,這個更容易出錯,很多人喜歡使用parseInt(),其實parseInt()是用于將字符串轉(zhuǎn)換成數(shù)字,而不是浮點數(shù)和整型之間的轉(zhuǎn)換,我們應(yīng)該使用Math.floor()或者Math.round()。
對象查找中的問題不一樣,Math是內(nèi)部對象,所以Math.floor()其實并沒有多少查詢方法和調(diào)用的時間,速度是最快的。
2.自定義的對象,推薦定義和使用toString()方法來進行類型轉(zhuǎn)換。
對于自定義的對象,如果定義了toString()方法來進行類型轉(zhuǎn)換的話,推薦顯式調(diào)用toString()。因為內(nèi)部的操作在嘗試所有可能性之后,會嘗試對象的toString()方法嘗試能否轉(zhuǎn)化為String,所以直接調(diào)用這個方法效率會更高。
4.2.5 循環(huán)的優(yōu)化
1.盡可能少使用for(in)循環(huán)。
在JavaScript中,我們可以使用for(;;),while(),for(in)三種循環(huán),事實上,這三種循環(huán)中for(in)的效率極差,因為他需要查詢散列鍵,只要可以就應(yīng)該盡量少用。
2.預(yù)先計算collection的length。
如:將for(var i = 0;i < collection.length;i++)
替換成:for(var i = 0, len = collection.length;i < len;i++)
效果會更好,尤其是在大循環(huán)中。
3.盡量減少循環(huán)內(nèi)的操作。
循環(huán)內(nèi)的每個操作,都會被放大為循環(huán)次數(shù)的倍數(shù)。所以,大循環(huán)內(nèi)微小的改進,在性能的整體提升上都是可觀的。
4.使用循環(huán)替代遞歸。
相比循環(huán),遞歸的效率更差一些。遞歸的優(yōu)點是在形式上更自然一些。所以,在不影響代碼的維護性的前提下,用循環(huán)替代遞歸。
4.2.6 其它方面
1.盡量使用語言內(nèi)置的語法。
”var arr = [?];“和”var arr = new Array(?);“是等效的,但是前者的效能優(yōu)于后者。同樣,”var foo = {};“的方式也比”var foo = new Object();“快;”var reg = /../;“要比”var reg=new RegExp()“快。
2.盡量不要使用eval。
使用eval,相當(dāng)于在運行時再次調(diào)用解釋引擎,對傳入的內(nèi)容解釋運行,需要消耗大量時間。
3.使用prototype代替closure。
使用closure在性能和內(nèi)存消耗上都是不利的。如果closure使用量過大,這就會成為一個問題。所以,盡量將:
this.methodFoo = function()
替換成:
MyClass.protoype.methodFoo = function()
和closure存在于對象實例之中不同,prototype存在于類中,被該類的所有的對象實例共享。
4.避免使用with語句。
With語句臨時擴展對象查找的范圍,節(jié)省了文字的錄入時間,但付出了更多的執(zhí)行時間。因為每個給出的名稱都要在全局范圍查找。所以,可以將下面的代碼:
with(document.formname){
field1.value = ”one“;field2.value = ”two“;}
變更為:
var form = document.formname;form.field1.value = ”one“;form.field2.value = ”two“;
4.3 DOM相關(guān)
4.3.1 創(chuàng)建DOM節(jié)點
相比較通過document.write來給頁面生成內(nèi)容,找一個容器元素(比如指定一個div或者span)并設(shè)置他們的innerHTML效率更高。而設(shè)置innerHTML的方式比通過createElement方法創(chuàng)建節(jié)點的效率更高。事實上,設(shè)置元素的innerHTML是創(chuàng)建節(jié)點效率最高的一種方式。
如果必須使用createElement方法,而如果文檔中存在現(xiàn)成的樣板節(jié)點,應(yīng)該是用cloneNode()方法。因為使用createElement()方法之后,你需要設(shè)置多次元素的屬性,使用cloneNode()則可以減少屬性的設(shè)置次數(shù)。同樣,如果需要創(chuàng)建很多元素,應(yīng)該先準(zhǔn)備一個樣板節(jié)點。
4.3.2 離線操作大型的DOM樹
在添加一個復(fù)雜的DOM樹時,可以先構(gòu)造,構(gòu)造結(jié)束后再將其添加到DOM數(shù)的適當(dāng)節(jié)點。這能夠節(jié)省界面刷新的時間。
同樣,在準(zhǔn)備編輯一個復(fù)雜的樹時,可以先將樹從DOM樹上刪除,等編輯結(jié)束后再添加回來。
4.3.3 對象查詢
使用[”“]查詢要比.item()更快。調(diào)用.item()增加了一次查詢和函數(shù)的調(diào)用。
4.3.4 定時器
如果針對的是不斷運行的代碼,不應(yīng)該使用setTimeout,而應(yīng)該用setInterval。setTimeout每次要重新設(shè)置一個定時器。
4.4 其他
1.盡量減小文件尺寸。
將JScript文件中無關(guān)的空行、空格、注釋去掉,有助于減小JS文件的尺寸,提高下載的時間。(可以通過工具來支持代碼發(fā)布)
2.盡量不要在同一個Page內(nèi)同時引用JScript和VBScript引擎
3.將Page內(nèi)的JScript移入到單獨的JS文件中。
4.將Page內(nèi)的JScript放置在Page的最下面,有助于提高頁面的響應(yīng)速度。
5.利用cache,減少JScript文件的下載次數(shù)
6.在HTML內(nèi)書寫JScript文件的URL時,注意統(tǒng)一大小寫。這樣可以利用前面URL緩存的文件。
C# 性能優(yōu)化——三種字符串拼接效率
2011年04月07日 星期四 17:56 字符串拼接主要包括三類:+,String.Format(),StringBuilder.Append()1)對于少量固定的字符串拼接,如string s= ”a“ + ”b“ + ”c“,系統(tǒng)會優(yōu)化成s= String.Concat(”a“,”b“,”c“),不會新建多個字符串。
如果寫成string s=”a“;s +=”b“;s+=”c“;則會創(chuàng)建三個新的字符串。2)String.Format的源代碼: public static String Format(IFormatProvider provider, String format, params Object[] args){ if(format == null || args == null)throw new ArgumentNullException((format==null)?”format“:”args“);StringBuilder sb = new StringBuilder(format.Length + args.Length * 8);sb.AppendFormat(provider,format,args);return sb.ToString();} 可見,它和StringBuilder有著相似的效率,比用“+”的拼接方式高效,并且代碼易于閱讀。
string s= String.Format(”{0}{1}{2}“,”a“,”b“,”c");3)StringBuilder可以指定內(nèi)存空間的容量,但可能需要進行數(shù)據(jù)類型轉(zhuǎn)化。字符串較少時,可以使用String.Format()代替。
4)少量的字符串操作時,可以使用“+”或者String.Format();大量的字符串操作時,比如在循環(huán)體內(nèi),必須使用StringBuilder.Append()。