ASP.NET夜话之21:asp.net网站的性能优化
生活随笔
收集整理的這篇文章主要介紹了
ASP.NET夜话之21:asp.net网站的性能优化
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
本篇主要講述在ASP.NET中如何提高程序性能。提高程序性能的方法主要從編碼和數(shù)據(jù)操作及優(yōu)化配置三方面,本章要講述的知識(shí)點(diǎn)有: l 程序編碼優(yōu)化 l 數(shù)據(jù)操作優(yōu)化 l 配置優(yōu)化 l 總結(jié)
21.1 程序編碼優(yōu)化
從編碼方面提高程序性能的方法主要涉及到集合操作、字符串連接、類型轉(zhuǎn)換等。21.1.1 集合操作
在.NET Framework中提供了很多集合類,如ArrayList、BitArray、Hashtable、Queue、SortedList、Stack、ListDictionary、NameValueCollection、OrderedDictionary、StringCollection、List<T>及數(shù)組等,要了解各個(gè)集合的特性,選擇合適的集合。在所有的集合中數(shù)組是性能最高的,如果要存儲(chǔ)的數(shù)據(jù)類型一致和容量固定,特別是對(duì)值類型的數(shù)組進(jìn)行操作時(shí)沒有裝箱和拆箱操作,效率極高。 在選擇集合類型時(shí)應(yīng)考慮幾點(diǎn): (1)集合中的元素類型是否是一致的,比如集合中將要存儲(chǔ)的元素都是int或者都是string類型的就可以考慮使用數(shù)組或者泛型集合,這樣在存儲(chǔ)數(shù)值類型元素就可以避免裝箱拆箱操作,即使是引用類型的元素也可以避免類型轉(zhuǎn)換操作。 (2)集合中的元素個(gè)數(shù)是否是固定的,如果集合中存儲(chǔ)的元素是固定的并且元素類型是一致的就可以使用數(shù)組來存儲(chǔ)。 (3)將來對(duì)集合的操作集中在那些方面,如果對(duì)集合的操作以查找居多可以考慮HashTable或者Dictionary<TKey,TValue>這樣的集合,因?yàn)樵?NET Framework中對(duì)這類集合采用了特殊機(jī)制,所以在查找時(shí)比較的次數(shù)比其它集合要少。 另外,在使用可變集合時(shí)如果不制定初始容量大小,系統(tǒng)會(huì)使用一個(gè)默認(rèn)值來指定可變集合的初始容量大小,如果將來元素個(gè)數(shù)超過初始容量大小就會(huì)先在內(nèi)部重新構(gòu)建一個(gè)集合,再將原來集合中的元素復(fù)制到新集合中,可以在實(shí)例化可變集合時(shí)指定一個(gè)相對(duì)較大的初始容量,這樣在向可變集合中添加大量元素時(shí)就可以避免集合擴(kuò)充容量帶來的性能損失。 下面以一個(gè)例子演示一下數(shù)組、ArrayList及List<T>集合操作的例子。頁(yè)面的設(shè)計(jì)代碼如下: using System; using System.Collections; using System.Collections.Generic; Technorati 標(biāo)簽: ASP.NET,優(yōu)化,ASP.NET夜話 /// <summary> /// 用來測(cè)試對(duì)集合進(jìn)行操作所花費(fèi)的時(shí)間 /// </summary> public class CollectionDemo { public static void Main() { Test(100000); Console.WriteLine("=========="); Test(1000000); Console.WriteLine("=========="); Test(10000000); Console.ReadLine(); } /// <summary> /// 操作數(shù)組 /// </summary> /// <param name="maxCount">要操作的次數(shù)</param> /// <returns></returns> private static TimeSpan ArrayOperation(int maxCount) { //在程序開始運(yùn)行時(shí)記錄下系統(tǒng)當(dāng)前時(shí)間 DateTime start = DateTime.Now; int[] intList = new int[maxCount]; int j = 0; for (int i = 0; i < maxCount; i++) { intList[i] = i; } for (int i = 0; i < maxCount; i++) { j = intList[i]; } //在程序結(jié)束后記錄下系統(tǒng)當(dāng)前時(shí)間 DateTime end = DateTime.Now; //用程序結(jié)束的系統(tǒng)時(shí)間減去開始運(yùn)行時(shí)的時(shí)間就是代碼運(yùn)行時(shí)間 return end - start; } /// <summary> /// /// </summary> /// <param name="maxCount"></param> /// <returns></returns> private static TimeSpan ArrayListOperation(int maxCount) { //在程序開始運(yùn)行時(shí)記錄下系統(tǒng)當(dāng)前時(shí)間 DateTime start = DateTime.Now; //用默認(rèn)的容量來初始化ArrayList //ArrayList intList = new ArrayList(); //用指定的容量來初始化ArrayList ArrayList intList = new ArrayList(maxCount); int j = 0; for (int i = 0; i < maxCount; i++) { intList.Add(i); } for (int i = 0; i < maxCount; i++) { j = (int)intList[i]; } //在程序結(jié)束后記錄下系統(tǒng)當(dāng)前時(shí)間 DateTime end = DateTime.Now; return end - start; } /// <summary> /// /// </summary> /// <param name="maxCount"></param> /// <returns></returns> private static TimeSpan GenericListOperation(int maxCount) { //在程序開始運(yùn)行時(shí)記錄下系統(tǒng)當(dāng)前時(shí)間 DateTime start = DateTime.Now; //用默認(rèn)的容量來初始化泛型集合 //List<int> intList = new List<int>(); //用指定的容量來初始化泛型集合 List<int> intList = new List<int>(maxCount); int j = 0; for (int i = 0; i < maxCount; i++) { intList.Add(i); } for (int i = 0; i < maxCount; i++) { j = intList[i]; } //在程序結(jié)束后記錄下系統(tǒng)當(dāng)前時(shí)間 DateTime end = DateTime.Now; return end - start; } private static void Test(int maxCount) { TimeSpan ts1 = ArrayOperation(maxCount); TimeSpan ts2 = ArrayListOperation(maxCount); TimeSpan ts3 = GenericListOperation(maxCount); Console.WriteLine("執(zhí)行" + maxCount + "次操作:"); Console.WriteLine("數(shù)組耗時(shí)" + ts1.TotalMilliseconds + "毫秒"); Console.WriteLine("ArrayList耗時(shí)" + ts2.TotalMilliseconds + "毫秒"); Console.WriteLine("泛型集合耗時(shí)" + ts3.TotalMilliseconds + "毫秒"); } } 對(duì)上面的程序代碼做幾點(diǎn)說明: (1)上面的代碼僅僅是給集合中的元素賦值,然后將集合中的元素取出來,分別用了數(shù)組、ArrayList和List<T>泛型集合,并且操作了不同的次數(shù)。 (2)在開始運(yùn)行時(shí)獲取到系統(tǒng)的當(dāng)前時(shí)間,然后在運(yùn)行結(jié)束之后再次獲取系統(tǒng)時(shí)間,兩次時(shí)間之差就是程序運(yùn)行這段代碼所花費(fèi)的時(shí)間,這是一個(gè)TimeSpan類型的變量。 (3)為了將測(cè)試結(jié)果放大,所以操作的次數(shù)要盡量設(shè)置大一點(diǎn),實(shí)際在網(wǎng)站運(yùn)行中程序代碼也會(huì)被成千上萬次運(yùn)行,所以這么做是可以接受的,也使得比較更明顯,并且這樣也可以減小某些偶然因素帶來的干擾。 因?yàn)樵贏SP.NET中測(cè)試不穩(wěn)定因素太多,所以這部分代碼是以控制臺(tái)程序來運(yùn)行的,運(yùn)行上面的代碼可得到如圖21-1所示的效果: 圖21-1 程序執(zhí)行結(jié)果 4.利用Stopwatch類也可以方便地實(shí)現(xiàn)上面的計(jì)時(shí)功能,對(duì)于上面用到的GenericListOperation(int maxCount)方法,如果使用Stopwatch類的話,代碼如下: private static TimeSpan GenericListOperation(int maxCount) { System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start();//在程序開始運(yùn)行時(shí)開始計(jì)時(shí) List<int> intList = new List<int>(maxCount); int j = 0; for (int i = 0; i < maxCount; i++) { intList.Add(i); } for (int i = 0; i < maxCount; i++) { j = intList[i]; } stopwatch.Stop();//在程序結(jié)束后停止計(jì)時(shí) return stopwatch.Elapsed; } 在上面的代碼中我們是采用了指定ArrayList和List<int>泛型集合的初始化容量大小,可以看出操作在集合元素固定的情況下,數(shù)組的操作是最快的,泛型集合的操作次之,ArrayList最慢。 以上測(cè)試是針對(duì)值類型數(shù)據(jù)的測(cè)試,如果是String這類的引用類型也會(huì)有類似的效果,只不過效果引用類型作為集合元素沒有值類型作為集合元素明顯。21.1.2 字符串連接優(yōu)化
在.NET Framework中String類是一個(gè)比較特殊的類,我們知道值類型變量直接在棧中分配內(nèi)存來存儲(chǔ)變量的值,并且不需要垃圾回收器來回收,大部分引用類型變量是在堆中分配內(nèi)存來存儲(chǔ)變量的值,在不再使用的情況下會(huì)被垃圾回收器回收所占用的內(nèi)存。String類型的變量雖然是引用類型變量(常用的賦值方式卻很類似于值類型變量的賦值方式,如string a=”123”),但是CLR(Common Language Runtime,通用語言運(yùn)行時(shí))通過了一種特殊的方法來存放字符串,CLR會(huì)維護(hù)一個(gè)會(huì)自動(dòng)維護(hù)一個(gè)名為“拘留池”(intern pool,不知道為什么微軟會(huì)這么叫) 的表,它包含在程序中聲明的每個(gè)唯一字符串常數(shù)的單個(gè)實(shí)例,以及以編程方式添加的 String 的任何唯一實(shí)例。該拘留池節(jié)約字符串存儲(chǔ)區(qū)。如果將字符串常數(shù)分配給幾個(gè)變量,則每個(gè)變量設(shè)置為引用“拘留池”(intern pool) 中的同一常數(shù),而不是引用具有相同值的 String 的幾個(gè)不同實(shí)例。 看如下代碼: String a=”abc”; String b=”abc”; 在上面的代碼中變量a和變量b都指向了堆中的同一個(gè)引用,也就是和下面的代碼是等效的: String a=”abc”; String b=a; 在給字符串變量賦值時(shí)會(huì)首先在“拘留池”中檢查是否有與要賦值的值相等的字符串,如果存在就會(huì)返回該字符串的引用,如果不存在就向字符串“駐留池”中添加該字符串,并且將該字符串的引用返回。這樣一來在每次連接字符串時(shí)都有可能創(chuàng)建新的字符串對(duì)象(如果“駐留池”中不存在對(duì)應(yīng)的字符串的話),從而導(dǎo)致了性能低下。 在String類有個(gè)方法專門用來檢測(cè)“拘留池”中是否存在指定字符串引用的方法,這個(gè)方法就是IsInterned(string str)方法,如果存在這個(gè)引用則返回str的引用,如果不存在這個(gè)引用就返回null。 在需要多次連接字符串時(shí)可以考慮使用System.Text.StringBuilder對(duì)象,這是一個(gè)可變?nèi)萘康淖址畬?duì)象。在實(shí)例化StringBuilder對(duì)象時(shí)會(huì)指定一個(gè)容量(如果不顯示指定,則系統(tǒng)默認(rèn)會(huì)指定初始容量為16,如果在程序中最終連接后的容量大于這個(gè)值可以自行指定一個(gè)較大的值作為初時(shí)容量,這樣也能提高性能),在進(jìn)行添加、插入及替換等修改操作時(shí)如果不超過容量,則會(huì)直接在緩沖區(qū)中操作,如果超過容量則會(huì)重新分配一個(gè)更大的緩沖區(qū),并將原來的數(shù)據(jù)復(fù)制到新緩沖區(qū)。 下面通過一個(gè)控制臺(tái)的例子來演示一下String類和StringBuilder類的區(qū)別,代碼如下: using System; using System.Text; public class StringDemo { public static void Main() { //如果存在"abc"字符串則a="abc",否則a=null; string a = string.IsInterned("abc"); string b = new StringBuilder().Append("as").Append("df").ToString(); if (string.IsNullOrEmpty(a)) { Console.WriteLine(a + "存在'拘留池’中"); } else { Console.WriteLine(a + "不存在'拘留池’中"); } if (string.IsNullOrEmpty(b)) { Console.WriteLine(b + "存在'拘留池’中"); } else { Console.WriteLine(b + "不存在'拘留池’中"); } int count = 9000; TimeSpan ts1 = StringConcat(count); TimeSpan ts2 = StringBuilderConcat(count); Console.WriteLine("使用了String來連接"+count.ToString()+"次耗時(shí)"+ts1.TotalMilliseconds+"毫秒"); Console.WriteLine("使用StringBuilder來連接" + count.ToString() + "次耗時(shí)" + ts2.TotalMilliseconds + "毫秒"); Console.ReadLine(); } /// <summary> /// 用String對(duì)象來連接字符串 /// </summary> /// <param name="count"></param> /// <returns></returns> public static TimeSpan StringConcat(int count) { DateTime start = DateTime.Now; string text = string.Empty; for (int i = 0; i < count; i++) { text=text+"0"+i; } DateTime end = DateTime.Now; return end - start; } /// <summary> /// 用StringBuilder對(duì)象來連接字符串 /// </summary> /// <param name="count"></param> /// <returns></returns> public static TimeSpan StringBuilderConcat(int count) { DateTime start = DateTime.Now; StringBuilder text = new StringBuilder(); for (int i = 0; i < count; i++) { text.Append("0"+i); } DateTime end = DateTime.Now; return end - start; } } 這個(gè)程序的運(yùn)行效果如圖21-2所示: 圖21-2 String類和StringBuilder類連接字符串的運(yùn)行效果21.1.3 類型轉(zhuǎn)換優(yōu)化
在開發(fā)中經(jīng)常會(huì)遇到類型轉(zhuǎn)換的問題,一種情況是由字符串類型轉(zhuǎn)換成數(shù)值類型,另一種情況是存在繼承關(guān)系或者實(shí)現(xiàn)關(guān)系的類之間進(jìn)行類型轉(zhuǎn)換。在上面的兩種轉(zhuǎn)換中如果存在不能轉(zhuǎn)換的情況,則會(huì)拋出異常,在引發(fā)和處理異常時(shí)將消耗大量的系統(tǒng)資源和執(zhí)行時(shí)間。引發(fā)異常是為了確實(shí)處理異常情況,而不是為了處理可預(yù)知的時(shí)間或控制流(這一點(diǎn)尤其要注意,不要在代碼中來使用異常進(jìn)行流程控制)。21.1.3.1 字符串類型向值類型轉(zhuǎn)換
在.NET Framework2.0版本以前將字符串類型轉(zhuǎn)換成數(shù)值類型都是使用Parse()方法,如int.Parse("123")、char.Parse("a")及bool.Parse("TrueString")等等,如果出現(xiàn)了指定的字符串不能轉(zhuǎn)換成相應(yīng)的數(shù)值類型時(shí)就會(huì)拋出異常,可能會(huì)對(duì)性能造成不良的影響。在.NET Framework2.0及以后版本中增加了TryParse()方法,減小了性能問題。TryParse()方法使用了兩個(gè)參數(shù):第一個(gè)參數(shù)是要轉(zhuǎn)換成數(shù)值類型的字符串,第二個(gè)參數(shù)是一個(gè)帶有out關(guān)鍵字的參數(shù),并且這個(gè)方法有返回值,指示指定的字符串是否能轉(zhuǎn)換成相應(yīng)的數(shù)據(jù)類型。如果指定的字符串能轉(zhuǎn)換成相應(yīng)的數(shù)據(jù)類型則方法返回true,out參數(shù)就是指定字符串轉(zhuǎn)換成相應(yīng)數(shù)值的結(jié)果,否則方法返回false,表示不能進(jìn)行轉(zhuǎn)換而不會(huì)拋出異常。 其用法如下面的代碼所示: string str1 = "123"; string str2 = "ed1"; //因?yàn)樽鳛閛ut參數(shù),所以即使是據(jù)不變量也不用賦值 int number1; //因?yàn)樽鳛閛ut參數(shù),所以即使是據(jù)不變量也不用賦值 int number2; //"123"能轉(zhuǎn)換成int,所以b1=true,number1=123 bool b1 = int.TryParse(str1, out number1); //"ed1"不能轉(zhuǎn)換成int,所以b2=false bool b2 = int.TryParse(str2, out number2);21.1.3.2 引用類型之間轉(zhuǎn)換 在引用類型之間轉(zhuǎn)換有兩種方式:強(qiáng)制轉(zhuǎn)換和as轉(zhuǎn)換。下面是強(qiáng)制轉(zhuǎn)換的例子: object d = "asdf"; //將d牽制轉(zhuǎn)換成string類型 string e = (string)d; 同字符串類型轉(zhuǎn)換成數(shù)值類型一樣,如果不存在對(duì)應(yīng)的轉(zhuǎn)換關(guān)系也會(huì)拋出異常。為了避免引用類型之間轉(zhuǎn)換拋出異常,可以使用as關(guān)鍵字來轉(zhuǎn)換,如下面的代碼: object d ="123"; //下面的轉(zhuǎn)換會(huì)拋出異常 StringBuilder e = (StringBuilder)d; //下面的轉(zhuǎn)換不會(huì)拋出異常,并且f為null StringBuilder f = d as StringBuilder; 在C#中還有一個(gè)is關(guān)鍵字,它用來檢查對(duì)象是否與給定類型兼容,如果兼容表達(dá)式的值為true,否則為false。例如下面的表達(dá)式: string a = "asdf"; //b1=true,因?yàn)閟tring類實(shí)現(xiàn)了ICloneable接口 bool b1 = a is ICloneable; //b2=true,因?yàn)閟tring類是object類的派生類 bool b2 = a is object; //b3=false,因?yàn)閟tring類與StringBuilder類之間不存在派生或者實(shí)現(xiàn)關(guān)系 bool b3 = a is StringBuilder; 假如有A類型的變量a和B類型,對(duì)于A c=a as B這個(gè)轉(zhuǎn)換,存在如下情況:如果A實(shí)現(xiàn)或者派生自B,那么上面的轉(zhuǎn)換成功,否則轉(zhuǎn)換不成功,c為null,并且不會(huì)拋出異常。 上面講到的都是關(guān)于編碼方面提高程序性能應(yīng)該注意的實(shí)現(xiàn),此外在編碼過程中還應(yīng)注意盡量減少裝箱拆箱操作。裝箱操作是指將值類型轉(zhuǎn)換成引用類型,拆箱操作是指將引用類型轉(zhuǎn)換成值類型,通過裝箱操作使得值類型可以被視作對(duì)象。相對(duì)于簡(jiǎn)單的賦值而言,裝箱和取消裝箱過程需要進(jìn)行大量的計(jì)算。對(duì)值類型進(jìn)行裝箱時(shí),必須分配并構(gòu)造一個(gè)全新的對(duì)象。次之,取消裝箱所需的強(qiáng)制轉(zhuǎn)換也需要進(jìn)行大量的計(jì)算。在向ArrayList這樣的非范型集合中添加值類型元素時(shí)就會(huì)存在裝箱過程,再?gòu)姆欠缎图现腥〕鲋殿愋偷闹稻蜁?huì)存在拆箱過程。21.1.4 使用Server.Transfer()方法
使用Server.Transfer()方法實(shí)現(xiàn)同一應(yīng)用程序下不同頁(yè)面間的重定向可以避免不必要的客戶端頁(yè)面重定向。它比Response.Redirect()方法性能要高,并且Server.Transfer()方法具有允許目標(biāo)頁(yè)從源頁(yè)中讀取控件值和公共屬性值的優(yōu)點(diǎn)。由于調(diào)用了這個(gè)方法之后瀏覽器上不會(huì)反應(yīng)更改后的頁(yè)的信息,因此它也適合以隱藏URL的形式向用戶呈現(xiàn)頁(yè)面,不過如果用戶點(diǎn)擊了瀏覽器上的“后退“按鈕或者刷新頁(yè)面有可能導(dǎo)致意外情況。21.1.5 避免不必要的服務(wù)器往返
雖然使用服務(wù)器控件能夠節(jié)省時(shí)間和代碼,但是使用服務(wù)器控件有時(shí)間會(huì)增加頁(yè)面的往返次數(shù),如果在頁(yè)面中使用了數(shù)據(jù)綁定控件,在默認(rèn)情況下每次響應(yīng)客戶端回發(fā)而加載頁(yè)面時(shí)都會(huì)重新綁定數(shù)據(jù),其實(shí)在很多情況下這個(gè)過程是沒有必要的,使用 Page.IsPostBack 避免對(duì)往返過程執(zhí)行不必要的處理,這個(gè)在數(shù)據(jù)綁定控件那一章有所體現(xiàn)。21.1.6 盡早釋放對(duì)象
在.NET Framework中有很多類實(shí)現(xiàn)了IDisposable接口,實(shí)現(xiàn)了IDisposable接口的類中都會(huì)有一個(gè)Dispose()方法,當(dāng)這些類的實(shí)例不再使用時(shí),應(yīng)及早調(diào)用該類的Dispose()方法以釋放所占用的資源。21.1.7 盡量減少服務(wù)器控件的使用 服務(wù)器控件在編程中使用起來確實(shí)方便,但是這種方便是犧牲了一定的性能為前提的,比如需要在頁(yè)面某個(gè)地方顯示一個(gè)字符串,這個(gè)字符串在任何時(shí)候都不會(huì)發(fā)生變化,那么可以在HTML代碼中直接輸出,還有有些表單要實(shí)現(xiàn)點(diǎn)擊按鈕之后清空表單輸入,利用HTML中的重置按鈕就可以完成這個(gè)功能,都沒有必要使用服務(wù)器控件。21.2 數(shù)據(jù)操作優(yōu)化
數(shù)據(jù)操作優(yōu)化方面主要是數(shù)據(jù)訪問優(yōu)化,主要有數(shù)據(jù)庫(kù)連接對(duì)象使用、數(shù)據(jù)訪問優(yōu)化、優(yōu)化SQL語句、使用緩存等。21.2.1 數(shù)據(jù)庫(kù)連接對(duì)象使用優(yōu)化
對(duì)于數(shù)據(jù)庫(kù)連接的使用始終遵循的一條原則是:盡可能晚打開數(shù)據(jù)庫(kù)連接,盡可能早關(guān)閉數(shù)據(jù)庫(kù)連接。這個(gè)在ADO.NET一章作過講述,再次不在贅述。 除此之外,還可以使用數(shù)據(jù)庫(kù)連接池來優(yōu)化。連接到數(shù)據(jù)庫(kù)通常需要幾個(gè)需要很長(zhǎng)時(shí)間的步驟組成,如建立物理通道(例如套接字或命名管道)、與服務(wù)器進(jìn)行初次握手、分析連接字符串信息、由服務(wù)器對(duì)連接進(jìn)行身份驗(yàn)證、運(yùn)行檢查以便在當(dāng)前事務(wù)中登記等等。實(shí)際上,大多數(shù)應(yīng)用程序僅使用一個(gè)或幾個(gè)不同的連接配置。這意味著在執(zhí)行應(yīng)用程序期間,許多相同的連接將反復(fù)地打開和關(guān)閉。為了使打開的連接成本最低,ADO.NET 使用稱為連接池的優(yōu)化方法。連接池減少新連接需要打開的次數(shù)。池進(jìn)程保持物理連接的所有權(quán)。通過為每個(gè)給定的連接配置保留一組活動(dòng)連接來管理連接。只要用戶在連接上調(diào)用 Open,池進(jìn)程就會(huì)檢查池中是否有可用的連接。如果某個(gè)池連接可用,會(huì)將該連接返回給調(diào)用者,而不是打開新連接。應(yīng)用程序在該連接上調(diào)用 Close 時(shí),池進(jìn)程會(huì)將連接返回到活動(dòng)連接池集中,而不是真正關(guān)閉連接。連接返回到池中之后,即可在下一個(gè) Open 調(diào)用中重復(fù)使用。 池連接可以大大提高應(yīng)用程序的性能和可縮放性。默認(rèn)情況下,ADO.NET 中啟用連接池。除非顯式禁用,否則,連接在應(yīng)用程序中打開和關(guān)閉時(shí),池進(jìn)程將對(duì)連接進(jìn)行優(yōu)化。在開發(fā)大型網(wǎng)站時(shí)可以更改默認(rèn)的數(shù)據(jù)庫(kù)連接池配置信息,例如可以增加數(shù)據(jù)庫(kù)連接池的最大連接數(shù)(默認(rèn)是100),如下面的代碼就是將數(shù)據(jù)庫(kù)連接池的最大連接數(shù)設(shè)為200: Data Source=(local);Initial Catalog=AspNetStudy;User ID=sa;Password=sa;Pooling=true;Min Pool Size=0;Max Pool Size=200 當(dāng)然也不是設(shè)置數(shù)據(jù)庫(kù)連接池的最大連接數(shù)越大越好,實(shí)際上還會(huì)受其它因素的限制。21.2.2 數(shù)據(jù)訪問優(yōu)化
如果對(duì)數(shù)據(jù)庫(kù)中的數(shù)據(jù)不是需要經(jīng)常讀取,可以使用相應(yīng)的DataReader對(duì)象來讀取(如SqlDataReader、OleDbDataReader或OracleDataReader),在這種情況下使用DataReader對(duì)象會(huì)得到一定的性能提升。 此外,在數(shù)據(jù)訪問時(shí)還可以使用存儲(chǔ)過程。使用存儲(chǔ)過程除了可以防范SQL注入之外,還可以提高程序性能和減少網(wǎng)絡(luò)流量。存儲(chǔ)過程是存儲(chǔ)在服務(wù)器上的一組預(yù)編譯的SQL語句,具有對(duì)數(shù)據(jù)庫(kù)立即訪問的功能,信息處理極為迅速。使用存儲(chǔ)過程可以避免對(duì)命令的多次編譯,在執(zhí)行一次后其執(zhí)行規(guī)劃就駐留在高速緩存中,以后需要時(shí)只需直接調(diào)用緩存中的二進(jìn)制代碼即可。21.2.3 優(yōu)化SQL語句
在開發(fā)中除了從C#代碼方面優(yōu)化數(shù)據(jù)訪問之外,還可以從SQL語句上優(yōu)化數(shù)據(jù)訪問。有人做過調(diào)查,在數(shù)據(jù)量大的庫(kù)中進(jìn)行數(shù)據(jù)訪問,不同的人編寫的SQL語句所花費(fèi)的時(shí)間有可能相差上百倍,因此盡量讓項(xiàng)目中對(duì)數(shù)據(jù)查詢優(yōu)化有經(jīng)驗(yàn)的人編寫SQL語句以提高程序性能。 在優(yōu)化SQL語句時(shí),有幾條原則需要注意: (1)盡量避免”select * from 表名”這樣的SQL語句,特別是在表中字段比較多而只需要顯示某幾個(gè)字段數(shù)據(jù)的情況下更應(yīng)該注意這個(gè)問題,比如針對(duì)SQL Server數(shù)據(jù)庫(kù)來說,如果不需要顯示或者操作表中的image、Text、ntext及xml這樣的字段,就盡量不要出現(xiàn)在select語句中的字段列表中。 (2)盡量不要在查詢語句中使用子查詢。 (3)盡量使用索引。索引是與表或視圖關(guān)聯(lián)的磁盤上結(jié)構(gòu),可以加快從表或視圖中檢索行的速度。索引包含由表或視圖中的一列或多列生成的鍵。這些鍵存儲(chǔ)在一個(gè)結(jié)構(gòu)中,使數(shù)據(jù)庫(kù)可以快速有效地查找與鍵值關(guān)聯(lián)的行。設(shè)計(jì)良好的索引可以減少磁盤 I/O 操作,并且消耗的系統(tǒng)資源也較少,從而可以提高查詢性能。對(duì)于包含 SELECT、UPDATE 或 DELETE 語句的各種查詢,索引會(huì)很有用。查詢優(yōu)化器使用索引時(shí),搜索索引鍵列,查找到查詢所需行的存儲(chǔ)位置,然后從該位置提取匹配行。通常,搜索索引比搜索表要快很多,因?yàn)樗饕c表不同,一般每行包含的列非常少,且行遵循排序順序。對(duì)于常用作where查詢的字段可以建立索引以提高查詢速度。注意,使用索引后會(huì)降低對(duì)表的插入、更新和刪除速度,在一張表上也不宜建立過多的索引。21.2.4 合理使用緩存
在ASP.NET中在不同級(jí)別提供了緩存功能,比如控件級(jí)和頁(yè)面級(jí)及全局級(jí)都提供了緩存功能,在控件中或者頁(yè)面中都可以通過@ OutputCache指令來使用緩存,這對(duì)于減少一些不經(jīng)常變化并且比較耗時(shí)的操作的性能損耗很有用。 除此之外,還有System.Web.Caching.Cache類對(duì)提高程序性能也非常有用,雖然利用Session或者Application也能實(shí)現(xiàn)在內(nèi)存中保存數(shù)據(jù),但是在Session中保存的數(shù)據(jù)只能被單個(gè)用戶使用,而在Application中使用的數(shù)據(jù)如果不手動(dòng)釋放就會(huì)一直保存在內(nèi)存當(dāng)中,利用Cache就完全克服了上面的缺點(diǎn)。Cache類提供了強(qiáng)大的功能,允許自定義緩存項(xiàng)及緩存時(shí)間和優(yōu)先級(jí)等,在服務(wù)器內(nèi)存不夠用時(shí)會(huì)自動(dòng)較少使用的或者優(yōu)先級(jí)比較低的項(xiàng)以釋放內(nèi)存。另外還可以指定緩存關(guān)聯(lián)依賴項(xiàng),如果緩存關(guān)聯(lián)依賴項(xiàng)發(fā)生改變緩存項(xiàng)就會(huì)實(shí)效并從緩存中移除。比如可以將一個(gè)經(jīng)常要讀取的文件的內(nèi)容緩存起來,并在文件上保留一個(gè)依賴項(xiàng),一旦文件內(nèi)容發(fā)生變化就會(huì)從內(nèi)存中移除緩存的文件內(nèi)容,可以再次從文件中重新讀取文件內(nèi)容到緩存中,這樣就保證了得到的文件內(nèi)容是最新的。 下面就是一個(gè)在ASP.NET使用Cache的例子,頁(yè)面的設(shè)計(jì)部分代碼如下: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="CacheDemo.aspx.cs" Inherits="day21_CacheDemo" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>使用Cache的例子</title> </head> <body> <form id="form1" runat="server"> <div> <asp:TextBox ID="txtContent" runat="server" Columns="50" Rows="10" TextMode="MultiLine"></asp:TextBox> <asp:Button ID="btnGetFileContent" runat="server" OnClick="btnGetFileContent_Click" Text="顯示文件內(nèi)容" /> </div> </form> </body> </html> 頁(yè)面的邏輯代碼如下: using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.IO; using System.Text; using System.Web.Caching; public partial class day21_CacheDemo : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void btnGetFileContent_Click(object sender, EventArgs e) { //從緩存中根據(jù)鍵讀取,并使用as轉(zhuǎn)換 string content = Cache["Content"] as string; //如果不存在緩存項(xiàng),則設(shè)置緩存 if (content== null) { String path = Server.MapPath("~/day21/Content.txt"); //注意這里沒有使用FileStream和StreamReader來讀取文件內(nèi)容 string text = File.ReadAllText(path, Encoding.UTF8); //創(chuàng)建緩存依賴項(xiàng) CacheDependency dependency = new CacheDependency(path); //創(chuàng)建當(dāng)緩存移除時(shí)調(diào)用的方法 CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(ItemRemovedCallBack); //添加緩存,并且設(shè)定緩存過期時(shí)間為30分鐘 Cache.Add("Content", text, dependency, Cache.NoAbsoluteExpiration, new TimeSpan(0, 30, 0), CacheItemPriority.Default, callBack); txtContent.Text = text; } else { txtContent.Text = content; } } /// <summary> /// 當(dāng)從緩存中移除緩存項(xiàng)時(shí)要調(diào)用的方法 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="reason"></param> private void ItemRemovedCallBack(string key, object value, CacheItemRemovedReason reason) { String path = Server.MapPath("~/day21/CacheChangeLog.txt"); string log=string.Format("鍵為{0}的緩存項(xiàng)于{1}被刪除,原因是:{2}。\r\n", key,DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ms"),reason.ToString()); //如果不存在文件下面的方法會(huì)創(chuàng)建文件并將內(nèi)容寫入到文件 //如果存在文件就會(huì)追加 File.AppendAllText(path, log); } } 為了達(dá)到演示效果,還需要在頁(yè)面所在文件夾下創(chuàng)建一個(gè)txt文件,文件內(nèi)容為“這是《ASP.NET夜話》第二十一章中進(jìn)行的Cache測(cè)試文件。”,并用UTF-8編碼保存,如圖21-3所示: 圖21-3 創(chuàng)建Cotent.txt文件并保存成UTF-8編碼 運(yùn)行頁(yè)面之后的效果如圖21-4所示: 圖21-4 頁(yè)面的初始運(yùn)行效果 點(diǎn)擊“顯示文件內(nèi)容”按鈕,因?yàn)槭状物@示在緩存中并不存在文件內(nèi)容,所以會(huì)將文件內(nèi)容讀取出來并在文本框中顯示。在文本框顯示了文件內(nèi)容之后,手動(dòng)修改Content.txt文件的內(nèi)容并保存,然后再次點(diǎn)擊顯示文件內(nèi)容,在文本框中就會(huì)顯示文件的最新內(nèi)容了,如圖21-5所示: 圖21-5 在修改文件內(nèi)容之后重新顯示文件內(nèi)容的效果 由此可見,使用了緩存關(guān)聯(lián)依賴項(xiàng)之后確實(shí)能移除緩存數(shù)據(jù),下次顯示時(shí)因?yàn)榫彺骓?xiàng)已經(jīng)被移除所以會(huì)重新讀取文件內(nèi)容并進(jìn)行緩存,因而就能看到最新的文件內(nèi)容。同時(shí),還能在Content.txt文件所在文件夾下看到一個(gè)CacheChangeLog.txt文件,這個(gè)文件的內(nèi)容如圖21-6所示: 圖21-6 CacheChangeLog.txt文件的內(nèi)容 總之,Cache對(duì)象是一個(gè)使用起來很靈活的對(duì)象,可以滿足復(fù)雜條件下的數(shù)據(jù)緩存要求,合理使用緩存有時(shí)候能提高數(shù)量級(jí)的性能。不過在使用緩存時(shí)也要注意一些事項(xiàng),比如不要緩存頻繁變化和很少使用的數(shù)據(jù),也不要將數(shù)據(jù)緩存的時(shí)間設(shè)置過短,否則不但不能提高性能,嚴(yán)重情況下反而會(huì)降低性能。21.3 配置優(yōu)化
不光從程序代碼上能提高程序性能,調(diào)用某些設(shè)置也能提高程序的性能。21.3.1 禁用調(diào)試模式
在開發(fā)過程中因?yàn)榻?jīng)常要進(jìn)行調(diào)試,所以配置將Web網(wǎng)站項(xiàng)目設(shè)置成允許調(diào)試模式,在部署網(wǎng)站時(shí)一定要禁用此模式,在運(yùn)行過程中使用調(diào)試模式將會(huì)使網(wǎng)站的性能受到很大影響。禁用調(diào)試模式是在web.config文件中設(shè)置,如下面的代碼就是禁用調(diào)試模式: <compilation debug="false">21.3.2 合理使用ViewState
在ASP.NET中為了維護(hù)服務(wù)器控件在HTTP請(qǐng)求之間維護(hù)其狀態(tài)啟用了服務(wù)器控件的視圖狀態(tài)。服務(wù)器控件視圖狀態(tài)為其所有屬性的累計(jì)值,這些值在后面的請(qǐng)求處理中作為變量傳遞給隱藏的字段,一般情況下這些值是經(jīng)過了一定的編碼或者加密處理之后再保存到隱藏字段中的,在后面的請(qǐng)求中再經(jīng)過反向處理得到原始的值,這些處理都是需要花費(fèi)時(shí)間的。有時(shí)候?yàn)榱颂岣邞?yīng)用程序的性能,在不需維護(hù)服務(wù)器控件的情況下可以禁用視圖狀態(tài)(默認(rèn)情況下是啟用視圖狀態(tài)的),特別是在使用數(shù)據(jù)綁定控件時(shí)一定要注意這個(gè)問題。 下面是一個(gè)使用GridView控件來顯示數(shù)據(jù)的例子,這個(gè)文件其實(shí)在講述GridView控件時(shí)曾用來作為例子,現(xiàn)在又再次用來作為例子,代碼如下: <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server"> protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { //默認(rèn)顯示第一頁(yè),不過在GridView中第一頁(yè)的頁(yè)索引是0 //注意:在C#中集合里的索引也都是以0開始 BindGridView(0); } } //指定綁定頁(yè)面的數(shù)據(jù) private void BindGridView(int pageIndex) { //實(shí)例化Connection對(duì)象 SqlConnection connection = new SqlConnection("Data Source=zhou\\sql2000;Initial Catalog=AspNetStudy;Persist Security Info=True;User ID=sa;Password=sa"); //實(shí)例化Command對(duì)象 SqlCommand command = new SqlCommand("select * from UserInfo", connection); SqlDataAdapter adapter = new SqlDataAdapter(command); DataTable data = new DataTable(); adapter.Fill(data); #region 注意這部分代碼可以在設(shè)計(jì)視圖中設(shè)置,不必寫在代碼里 gvUserList.AllowPaging = true;//設(shè)置允許自動(dòng)分頁(yè) //gvUserList.AutoGenerateColumns = false;//設(shè)置不允許自動(dòng)綁定列 gvUserList.PageSize = 5;//設(shè)置每頁(yè)顯示5條記錄 #endregion gvUserList.DataSource = data; gvUserList.PageIndex = pageIndex;//設(shè)置當(dāng)前顯示第幾頁(yè) gvUserList.DataBind(); } //翻頁(yè)事件 protected void gvUserList_PageIndexChanging(object sender, GridViewPageEventArgs e) { //指定新頁(yè)面,重新綁定數(shù)據(jù) BindGridView(e.NewPageIndex); } </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>用GridView顯示數(shù)據(jù)的例子(注意:采用單頁(yè)模式)</title> </head> <body> <form id="form1" runat="server"> <div> <asp:GridView ID="gvUserList" runat="server" AutoGenerateColumns="False" OnPageIndexChanging="gvUserList_PageIndexChanging"> <Columns> <asp:BoundField DataField="UserId" HeaderText="編號(hào)" /> <asp:HyperLinkField DataNavigateUrlFields="UserId" DataNavigateUrlFormatString="DetailsViewDemo.aspx?UserId={0}" DataTextField="RealName" HeaderText="查看" /> <asp:BoundField DataField="UserName" HeaderText="用戶名" > <ItemStyle BackColor="#C0FFC0" /> </asp:BoundField> <asp:BoundField DataField="RealName" HeaderText="真實(shí)姓名" /> <asp:BoundField DataField="Age" HeaderText="年齡" /> <asp:CheckBoxField DataField="Sex" HeaderText="男" /> <asp:BoundField DataField="Mobile" HeaderText="手機(jī)" /> <asp:TemplateField HeaderText="電子郵件"> <AlternatingItemTemplate> <a href='emailto:<%#Eval("Email") %>'>發(fā)郵件給<%#Eval("RealName") %></a> </AlternatingItemTemplate> <ItemTemplate> <%#Eval("Email") %> </ItemTemplate> </asp:TemplateField> </Columns> <EmptyDataTemplate> 溫馨提示:當(dāng)前沒有任何記錄哦。 </EmptyDataTemplate> </asp:GridView> </div> </form> </body> </html> 運(yùn)行這個(gè)頁(yè)面會(huì)看到如圖21-7所示的效果。 圖21-7 用GridView顯示用戶信息 如果看到這個(gè)頁(yè)面的最終生成的HTML頁(yè)面源代碼,會(huì)看到如圖21-8顯示的效果: 圖21-8 查看最終HTML頁(yè)面的源代碼效果 從上圖可以看出為了維護(hù)GridView控件的狀態(tài)所花費(fèi)的代價(jià)是客觀的,如果不需要維護(hù)這個(gè)狀態(tài)可以禁用GridView控件的視圖狀態(tài)(在大部分情況下都用不著)。具體操作是切換到設(shè)計(jì)視圖下,在頁(yè)面中選中GridView控件然后在屬性窗口中找到EnableViewState屬性并將其設(shè)為false,如圖21-9所示: 圖21-9 禁用GridView控件的視圖狀態(tài) 禁用GridView的視圖狀態(tài)之后再次運(yùn)行頁(yè)面并查看HTML源代碼,會(huì)看到如圖21-10所示的效果: 圖21-10 禁用GridView的視圖狀態(tài)之后生成的HTML源代碼 從圖21-8和圖21-10對(duì)比情況來看,禁用了視圖狀態(tài)之后生成的HTML代碼大小大大減少,不斷降低了網(wǎng)絡(luò)流量傳輸,還減輕了服務(wù)器的負(fù)擔(dān)。如果要禁用整個(gè)頁(yè)面的服務(wù)器控件的視圖狀態(tài),可以在頁(yè)面@Page指令中添加EnableViewState="false"值,如本實(shí)例中頁(yè)面禁用頁(yè)面中所有控件視圖狀態(tài)之后,頁(yè)面的@Page指令如下: <%@ Page Language="C#" EnableViewState="false" %> 最后要提示一點(diǎn)的是,禁用服務(wù)器控件的視圖狀態(tài)之后有可能有些服務(wù)器控件的內(nèi)置功能無法使用,例如在本利中禁用GridView控件的視圖狀態(tài)之后就沒有辦法使用內(nèi)置分頁(yè)功能了。21.3.3 合理選擇會(huì)話狀態(tài)存儲(chǔ)方式
ASP.NET中支持多種會(huì)話狀態(tài)數(shù)據(jù)存儲(chǔ)方式,這是一個(gè)SessionStateMode枚舉值,如下表所示: 數(shù)據(jù)存儲(chǔ)模式 說明 InProc 模式 將會(huì)話狀態(tài)數(shù)據(jù)保存在ASP.NET進(jìn)程中,只能用于單個(gè)服務(wù)器,Web服務(wù)器重其后不能保留會(huì)話狀態(tài),這是默認(rèn)的存儲(chǔ)方式 StateServer 模式 將會(huì)話狀態(tài)存儲(chǔ)在單獨(dú)的進(jìn)程中,可將會(huì)話狀態(tài)用于多個(gè)Web服務(wù)器,并在Web服務(wù)器重啟后還能保留會(huì)話狀態(tài) SQLServer 模式 會(huì)話數(shù)據(jù)保存到SQL Server 數(shù)據(jù)庫(kù)中,可將會(huì)話狀態(tài)用于多個(gè)Web服務(wù)器,并在Web服務(wù)器重啟后還能保留會(huì)話狀態(tài) Custom 模式 自定義存儲(chǔ)方式 Off 模式 禁用會(huì)話狀態(tài) 上面的每種方式都有字節(jié)的優(yōu)點(diǎn)和缺點(diǎn),InProc 模式是存儲(chǔ)會(huì)話狀態(tài)最快的解決方案,如果不需要保存大量數(shù)據(jù)并且不需要Web服務(wù)器重其后還能保存數(shù)據(jù),則建議使用這種方式。例如下面的設(shè)置就是用了使用了進(jìn)程內(nèi)存儲(chǔ)會(huì)話狀態(tài),并且設(shè)置Session的超期時(shí)間為20分鐘。 <configuration> <system.web> <sessionState mode="InProc" timeout="20"/> </system.web> </configuration>21.4 總結(jié)
上面提到的優(yōu)化方法是筆者平時(shí)做性能優(yōu)化時(shí)常用到的優(yōu)化方法,除了上面的方法之外還可以通過使用更好的算法來提高程序性能。盡管優(yōu)化的方法各異,但目的只有一個(gè):今最大可能在滿足程序要求的情況下提高性能。除了上面的方法之外還有其它的方式,希望讀者朋友在開發(fā)和學(xué)習(xí)中自己多積累這方面的經(jīng)驗(yàn)。總結(jié)
以上是生活随笔為你收集整理的ASP.NET夜话之21:asp.net网站的性能优化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: u是什么币
- 下一篇: asp.net ajax控件工具集 Au