提高ASP性能的最佳選擇
發(fā)表時間:2024-05-31 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]ASP開發(fā)人員為了在他們的設(shè)計項目中獲得更好的性能和可擴展性而不斷努力。幸運地是,有許多書籍和站點在這方面提供了很好的建議。但是這些建議的基礎(chǔ)都是從ASP平臺工作的結(jié)構(gòu)上所得出的結(jié)論,對實際獲得的性能的提高沒有量的測量。由于這些建議需要更加復(fù)雜的編碼過程并降低了編碼的可讀性,開發(fā)人員就只能在看不到...
ASP開發(fā)人員為了在他們的設(shè)計項目中獲得更好的性能和可擴展性而不斷努力。幸運地是,有許多書籍和站點在這方面提供了很好的建議。但是這些建議的基礎(chǔ)都是從ASP平臺工作的結(jié)構(gòu)上所得出的結(jié)論,對實際獲得的性能的提高沒有量的測量。由于這些建議需要更加復(fù)雜的編碼過程并降低了編碼的可讀性,開發(fā)人員就只能在看不到實際運行效果的情況下,獨自衡量為了提高他們ASP應(yīng)用程序的性能是否值得付出這些代價。
本文分為兩大部分,我將介紹一些性能測試結(jié)果,幫助開發(fā)人員來確定某一特定舉措是否不僅對將來的項目來說是值得的,并且能夠?qū)υ瓉淼捻椖窟M(jìn)行更新。在第一部分我將回顧一些ASP開發(fā)的基礎(chǔ)性問題。在第二部分,將涉及一些最優(yōu)化ADO函數(shù),并將它們的結(jié)果與調(diào)用VB COM對象執(zhí)行相同ADO函數(shù)的ASP頁面進(jìn)行比較。這些結(jié)果很讓人開眼界,甚至有些時候是很令人吃驚的。
在本文中,我們將回答以下問題:
* 將ASP生成的內(nèi)容寫入響應(yīng)流中最有效的方法是什么?
* 是否應(yīng)該開啟緩沖器?
* 是否應(yīng)該考慮向ASP代碼中增加注釋?
* 是否應(yīng)該為頁面明確地設(shè)置默認(rèn)語言?
* 如果不需要,是否應(yīng)該關(guān)閉Session 狀態(tài)?
* 是否應(yīng)該把腳本邏輯放在子程序和函數(shù)區(qū)中?
* 使用包含文件有什么影響?
* 執(zhí)行錯誤處理時會施加什么樣的負(fù)載?
* 設(shè)置一個上下文處理是否對性能有影響?
所有測試都是用Microsoft的Web應(yīng)用程序重點工具(WAST)來進(jìn)行的,這是一個免費的工具,可以在這里找到。我用WAST創(chuàng)建了一個簡單的test 腳本,反復(fù)調(diào)用下面所描述的ASP頁面測試(每個超過70,000次)。反應(yīng)的時間基于平均最后字節(jié)總時間(TTLB), 也就是從最初請求的時間到工具從服務(wù)器接收最后一位數(shù)據(jù)的時間。我們的測試服務(wù)器是一個Pentium 166,內(nèi)存為196MB,客戶機為Pentium 450,內(nèi)存為256MB。你也許會想這些機器的性能并不算很高級,但是不要忘了,我們并不是要測試服務(wù)器的容量,我們只是要測試服務(wù)器每次處理一個頁面所用的時間。測試期間這些機器不做其它工作。WAST 測試腳本、測試報告以及所有的ASP測試頁面都包含在ZIP文件中,你可以自己進(jìn)行回顧和測試。
將ASP生成的內(nèi)容寫入響應(yīng)流中最有效的方法是什么?
使用ASP的一個最主要原因是在服務(wù)器上生成動態(tài)內(nèi)容。所以很明顯,我們測試的起點是確定將動態(tài)內(nèi)容發(fā)送到響應(yīng)流中的最適合的方式。在多種選擇中,有兩個是最基本的:一是使用內(nèi)聯(lián)ASP標(biāo)記,另一個是使用Response.Write 語句。
為測試這些選擇,我們創(chuàng)建了一個簡單的ASP頁面,其中定義了一些變量,然后將它們的值插入表格中。雖然這個頁面很簡單也不是很實用,但它允許我們分離并測試一些單獨的問題。
使用ASP內(nèi)聯(lián)標(biāo)記
第一個測試包括使用內(nèi)聯(lián)ASP標(biāo)記< %= x % >,其中x是一個已賦值的變量。到目前為止,這個方法是最容易執(zhí)行的,并且它使頁面的HTML部分保持一種易于閱讀和維護(hù)的格式。
< % OPTION EXPLICIT
Dim FirstName
Dim LastName
Dim MiddleInitial
Dim Address
Dim City
Dim State
Dim PhoneNumber
Dim FaxNumber
Dim EMail
Dim BirthDate
FirstName = "John"
MiddleInitial = "Q"
LastName = "Public"
Address = "100 Main Street"
City = "New York"
State = "NY"
PhoneNumber = "1-212-555-1234"
FaxNumber = "1-212-555-1234"
EMail = "john@public.com"
BirthDate = "1/1/1950"
% >
< HTML >
< HEAD >
< TITLE >Response Test< / TITLE >
< /HEAD >
< BODY >
< H1 >Response Test< /H1 >
< TABLE >
< tr >< td >< b >First Name:< /b >< /td >< td >< %= FirstName % >< /td >< /tr >
< tr >< td >< b >Middle Initial:< /b >< /td >< td >< %= MiddleInitial % >< /td >< /tr >
< tr >< td >< b >Last Name:< /b >< /td >< td >< %= LastName % >< /td >< /tr >
< tr >< td >< b >Address:< /b >< /td >< td >< %= Address % >< /td >< /tr >
< tr >< td >< b >City:< /b >< /td >< td >< %= City % >< /td >< /tr >
< tr >< td >< b >State:< /b >< /td >< td >< %= State % >< /td >< /tr >
< tr >< td >< b >Phone Number:< /b >< /td >< td >< %= PhoneNumber % >< /td >< /tr >
< tr >< td >< b >Fax Number:< /b >< /td >< td >< %= FaxNumber % >< /td >< /tr >
< tr >< td >< b >EMail:< /b >< /td >< td >< %= EMail % >< /td >< /tr >
< tr >< td >< b >Birth Date:< /b >< /td >< td >< %= BirthDate % >< /td >< /tr >
< /TABLE >
< /BODY >
< /HTML >
/app1/response1.asp的完整代碼
以前的最佳(反應(yīng)速度) = 8.28 msec/page
在HTML的每一行使用Response.Write 語句
許多比較好的學(xué)習(xí)文檔建議避免使用前面的那種方法。其主要理由是,在輸出頁面和處理頁面施加反應(yīng)時間的過程中,如果web 服務(wù)器不得不在發(fā)送純HTML和處理腳本之間進(jìn)行轉(zhuǎn)換,就會發(fā)生一種被稱為上下文轉(zhuǎn)換的問題。大部分程序員一聽到這里,他們的第一反應(yīng)就是將原始的HTML的每一行都包裝在Response.Write函數(shù)中。
…
Response.Write("< html >")
Response.Write("< head >")
Response.Write(" < title >Response Test< /title >")
Response.Write("< /head >")
Response.Write("< body >")
Response.Write("< h1 >Response Test< /h1 >")
Response.Write("< table >")
Response.Write("< tr >< td >< b >First Name:< /b >< /td >< td >" & FirstName & "< /td >< /tr >")
Response.Write("< tr >< td >< b >Middle Initial:< /b >< /td >< td >" & MiddleInitial & "< /td >< /tr >")
…
/app1/response2.asp的片段
以前的最佳(反應(yīng)速度) = 8.28 msec/page
反應(yīng)時間 = 8.08 msec/page
差= -0.20 msec (減少 2.4%)
我們可以看到,使用這種方法與使用內(nèi)聯(lián)標(biāo)記的方法相比在性能上獲得的收益非常小,這也許是因為頁面給服務(wù)器裝載了一大堆小的函數(shù)調(diào)用。這種方法最大的缺點是,由于現(xiàn)在HTML都嵌入腳本中,所以腳本代碼變得更加冗長,更加難以閱讀和維護(hù)。
使用包裝函數(shù)
當(dāng)我們試圖使用Response.Write 語句這種方法時,最令人灰心的發(fā)現(xiàn)可能就是Response.Write 函數(shù)不能在每行的結(jié)尾處放置一個CRLF 。因此,當(dāng)你從瀏覽器中閱讀源代碼時,本來布置得非常好的HTML,現(xiàn)在成了沒有結(jié)束的一行。我想,你的下一個發(fā)現(xiàn)可能會更令你恐怖:在Response 對象中沒有其姊妹函數(shù)Writeln 。所以,一個很明顯的反應(yīng)就是為Response.Write 函數(shù)創(chuàng)建一個包裝函數(shù),以便給每一行都附加一個CRLF 。
…
writeCR("< tr >< td >< b >First Name:< /b >< /td >< td >" & FirstName & "< /td >< /tr >")
…
SUB writeCR(str)
Response.Write(str & vbCRLF)
END SUB
/app1/response4.asp的片段
以前的最佳(反應(yīng)速度)= 8.08 msec/page
反應(yīng)時間= 10.11 msec/page
差 = +2.03 msec (增加 25.1%)
當(dāng)然,由于這種方法有效地使函數(shù)調(diào)用次數(shù)加倍,其對性能的影響也很明顯,因此要不惜一切代價避免。具有諷刺意味的是CRLF也向反應(yīng)流中為每行增加了2個字節(jié),而這是瀏覽器不需要呈現(xiàn)到頁面上的。格式化良好的HTML所做的一切就是讓你的競爭者更容易閱讀你的HTML源代碼并理解你的設(shè)計。
將連續(xù)的Response.Write 連接到一個單獨語句中
不考慮我們前面用包裝函數(shù)進(jìn)行的測試,下一個合乎邏輯的步驟就是從單獨的Response.Write 語句中提取出所有的字符串,將它們連接到一個單獨語句中,這樣就減少了函數(shù)調(diào)用的次數(shù),極大地提高了頁面的性能。
…
Response.Write("< html >" & _
"< head >" & _
"< title >Response Test< /title >" & _
"< /head >" & _
"< body >" & _
"< h1 >Response Test< /h1 >" & _
"< table >" & _
"< tr >< td >< b >First Name:< /b >< /td >< td >" & FirstName & "< /td >< /tr >" & _
…
"< tr >< td >< b >Birth Date:< /b >< /td >< td >" & BirthDate & "< /td >< /tr >" & _
"< /table >" & _
"< /body >" & _
"< /html >")
/app1/response3.asp的片段
以前的最佳(反應(yīng)速度)= 8.08 msec/page
反應(yīng)時間 = 7.05 msec/page
差 = -1.03 msec (減少12.7%)
目前,這是最優(yōu)化的配置。
將連續(xù)的Response.Write 連接到一個單獨語句中,在每行結(jié)尾處增加一個CRLF
考慮到那些要求他們的源代碼從瀏覽器中看要很純粹的人,我用vbCRLF 常量在前面測試中每行的結(jié)尾處插入了一些回車,然后重新運行。
…
Response.Write("< html >" & vbCRLF & _
"< head >" & vbCRLF & _
" < title >Response Test< /title >" & vbCRLF & _
"< /head >" & vbCRLF & _
…
/app1/response5.asp的片段
前面的最佳(反應(yīng)速度)= 7.05 msec/page
反應(yīng)時間= 7.63 msec/page
差 = +0.58 msec (增加 8.5%)
運行的結(jié)果在性能上有一點降低,這也許是由于額外的串聯(lián)和增加的字符量。
回顧和觀測
從前面有關(guān)ASP輸出的測試中可以得出一些規(guī)則:
* 避免內(nèi)聯(lián)ASP的過多使用。
* 總是將連續(xù)Response.Write 語句連接進(jìn)一個單獨語句內(nèi)。
* 永遠(yuǎn)不要在Response.Write 周圍使用包裝函數(shù)來附加CRLF。
* 如果必須格式化HTML輸出,直接在Response.Write 語句內(nèi)附加CRLF。
是否應(yīng)該開啟緩沖器?
通過腳本程序啟動緩沖器
在ASP腳本的頂部包含Response.Buffer=True ,IIS就會將頁面的內(nèi)容緩存。
< % OPTION EXPLICIT
Response.Buffer = true
Dim FirstName
…
/app1/buffer__1.asp的片段
以前的最佳(反應(yīng)時間)= 7.05 msec/page
反應(yīng)時間 = 6.08 msec/page
差= -0.97 msec (降低13.7%)
性能得到了極大提高。但是等等,還能有更好的。
通過服務(wù)器配置啟動緩沖器
雖然在IIS 5.0中緩沖器是被默認(rèn)啟動的,但是在IIS 4.0中還必須手動來啟動它。這時要找到站點的Properties 對話框,在那里,從Home Directory 標(biāo)簽中選擇配置按鈕。然后在"App options"下選擇"enable buffering" 。對于這個測試,Response.Buffer 語句從腳本中被移走了。
以前的最佳= 7.05 msec/page
反應(yīng)時間 = 5.57 msec/page
差= -1.48 msec (降低 21.0%)
目前,這是我們所得到的最快反應(yīng)了,比我們以前最好情況下的反應(yīng)時間還要降低21%。從現(xiàn)在開始,我們以后的測試都要把這個反應(yīng)時間作為基準(zhǔn)值。
回顧及觀測
緩沖器是提高性能的好方法,所以把緩沖器設(shè)置成服務(wù)器的默認(rèn)值很有必要。如果因為某些原因,頁面不能正確地使緩沖器運行,只需要Response.Buffer=False 命令即可。緩沖器的一個缺點是在整個頁面處理完之前,用戶從服務(wù)器看不到任何東西。因此,在復(fù)雜頁面的處理期間,偶而調(diào)用一次Response.Flush 來更新用戶是個好主意。
現(xiàn)在在我們的規(guī)則中又增加了一條:總是通過服務(wù)器設(shè)置開啟緩沖器。
是否應(yīng)該考慮向ASP代碼中增加注釋?
大部分HTML開發(fā)人員都知道包含HTML注釋不是個好主意,首先會增加傳輸數(shù)據(jù)的規(guī)模,其次它們只是向別的開發(fā)人員提供有關(guān)你頁面組織的信息。但是ASP頁面上的注釋又如何呢?它們從來不離開服務(wù)器,但也確實要增加頁面的規(guī)模,因此必須用ASP進(jìn)行分解。
在這次的測試中,我們增加20條注釋,每條有80個字符,總共有1600個字符。
< % OPTION EXPLICIT
'-------------------------------------------------------------------------------
… 20 lines …
'-------------------------------------------------------------------------------
Dim FirstName
…
/app2/comment_1.asp片段
基準(zhǔn)= 5.57 msec/page
反應(yīng)時間= 5.58 msec/page
差 = +0.01 msec (增加 0.1%)
測試的結(jié)果是驚人的。雖然注釋幾乎相當(dāng)于文件本身的兩倍,但是它們的存在并沒有給反應(yīng)時間帶來很大的影響。所以說我們可以遵循以下規(guī)則:
只要使用適度,ASP注釋對性能的影響很小或根本沒有影響。
是否應(yīng)該為頁面明確地設(shè)置默認(rèn)語言?
IIS處理VBScript是默認(rèn)的設(shè)置,但是我看到,在大多數(shù)例子中還是用< %@LANGUAGE=VBSCRIPT% >聲明將語言明確地設(shè)置為VBScript 。我們的下一個測試將檢驗這個聲明的存在對性能有什么影響。
< %@ LANGUAGE=VBSCRIPT % >
< % OPTION EXPLICIT
Dim FirstName
…
/app2/language1.asp片段。
基準(zhǔn)值= 5.57 msec/page
反應(yīng)時間= 5.64 msec/page
差= +0.07 msec (增加1.2%)
可以看到,包含了語言的聲明對性能有一個輕微的影響。因此:
* 設(shè)置服務(wù)器的默認(rèn)語言配置以與站點上使用的語言相匹配。
* 除非你使用非默認(rèn)語言,不要設(shè)置語言聲明。
如果不需要,是否應(yīng)該關(guān)閉Session 狀態(tài)?
避免使用IIS的Session上下文有許多理由,那些已經(jīng)可以獨立成為一篇文章。我們現(xiàn)在試圖回答的問題是當(dāng)頁面不需要時,關(guān)閉Session上下文是否對性能提高有所幫助。從理論上講應(yīng)該是肯定的,因為這樣一來就不需要用頁面例示Session上下文了。
同緩沖器一樣,Session狀態(tài)也有兩種配置方法:通過腳本和通過服務(wù)器設(shè)置。
通過腳本關(guān)閉Session上下文
對于這個測試,要關(guān)閉頁面中的Session上下文,我增加一個Session狀態(tài)聲明。
< %@ ENABLESESSIONSTATE = FALSE % >
< % OPTION EXPLICIT
Dim FirstName
…
/app2/session_1.asp片段。
基準(zhǔn)值= 5.57 msec/page
反應(yīng)時間= 5.46 msec/page
差= -0.11 msec (降低2.0%)
只通過這樣一個小小的努力就得到了不錯的進(jìn)步。現(xiàn)在看看第二部分。
通過服務(wù)器配置關(guān)閉Session 上下文
要在服務(wù)器上關(guān)閉Session 上下文,請到站點的Properties 對話框。在Home Directory 標(biāo)簽上選擇Configuration 按鈕。然后在"App options"下取消"enable session state" 的選擇。我們在沒有ENABLESESSIONSTATE 聲明的情況下運行測試。
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時間= 5.14 msec/page
差= -0.43 msec (降低7.7%)
這是性能的又一個顯著提高。所以,我們的規(guī)則應(yīng)是:在不需要的情況下,總是在頁面或應(yīng)用程序的水平上關(guān)閉Session狀態(tài)。
使用Option Explicit 會使性能有實質(zhì)改變嗎?
在一個ASP頁面的頂部設(shè)置Option Explicit 以要求所有的變量在使用之前都要在頁面上進(jìn)行聲明。這有兩個原因。首先應(yīng)用程序可以更快地處理變量的存取。其次,這樣可以防止我們無意中錯用變量的名字。在這個測試中我們移走Option Explicit 引用和變量的Dim 聲明。
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時間= 6.12 msec/page
差 = +0.55 msec (9.8% 增加)、
盡管有一些代碼行從頁面中去掉了,反應(yīng)時間卻依然增加了。所以盡管使用Option explicit 有時候費時間,但是在性能上卻有很顯著的效果。因此我們又可以增加一條規(guī)則:在VBScript中總是使用Option explicit。
是否應(yīng)該把腳本邏輯放在子程序和函數(shù)區(qū)?
用函數(shù)和子程序來組織和管理代碼是一個很好的方法,特別是當(dāng)一個代碼區(qū)在頁面中多次使用的情況。缺點是要在系統(tǒng)上增加一個做相同工作的額外函數(shù)調(diào)用。子程序和函數(shù)的另一個問題是變量的范圍。從理論上說,在一個函數(shù)區(qū)內(nèi)指定變量更有效,F(xiàn)在我們看看這兩個方面如何發(fā)生作用。
將Response.Write 語句移入子程序
這個測試只是將Response.Write 語句移入一個子程序區(qū)內(nèi)。
…
CALL writeTable()
SUB writeTable()
Response.Write("< html >" & _
"< head >" & _
…
"< tr >< td >< b >EMail:< /b >< /td >< td >" & EMail & "< /td >< /tr >" & _
"< tr >< td >< b >Birth Date:< /b >< /td >< td >" & BirthDate & "< /td >< /tr >" & _
"< /table >" & _
"< /body >" & _
"< /html >")
END SUB
/app2/function1.asp片段
基準(zhǔn)值= 5.57 msec/page
反應(yīng)時間= 6.02 msec/page
差 = +0.45 msec (8.1% 增加)
同預(yù)料中一樣,子程序調(diào)用給頁面帶來了額外的負(fù)擔(dān)。
將所有腳本移入子程序中
在這個測試中,Response.write 語句與變量聲明都移入一個子程序區(qū)中。
< % OPTION EXPLICIT
CALL writeTable()
SUB writeTable()
Dim FirstName
…
Dim BirthDate
FirstName = "John"
…
BirthDate = "1/1/1950"
Response.Write("< html >" & _
"< head >" & _
" < title >Response Test< /title >" & _
"< /head >" & _
"< body >" & _
"< h1 >Response Test< /h1 >" & _
"< table >" & _
"< tr >< td >< b >First Name:< /b >< /td >< td >" & FirstName & "< /td >< /tr >" & _
…
"< tr >< td >< b >Birth Date:< /b >< /td >< td >" & BirthDate & "< /td >< /tr >" & _
"< /table >" & _
"< /body >" & _
"< /html >")
END SUB
/app2/function2.asp片段
基準(zhǔn)值= 5.57 msec/page
反應(yīng)時間= 5.22 msec/page
差 = -0.35 msec (6.3% 降低)
非常有趣!盡管將變量移到函數(shù)范圍內(nèi)增加了額外的函數(shù)調(diào)用,但實際上卻提高了性能。我們又可以增加以下規(guī)則:
* 在一個頁面上,如果代碼要使用一次以上,就將代碼封入函數(shù)區(qū)。
* 適當(dāng)時候,將變量聲明移到函數(shù)范圍內(nèi)。
使用包含文件有什么影響?
ASP編程的一個重要功能就是包含來自其它頁面的代碼。通過這項功能,程序員可以在多個頁面上共享函數(shù),使代碼更易于維護(hù)。缺點在于服務(wù)器必須從多個來源組裝頁面。以下是使用Include文件的兩個測試。
使用內(nèi)聯(lián)代碼的Include 文件
在這個測試中,有一小段代碼被移到一個Include 文件中:
< % OPTION EXPLICIT
Dim FirstName
…
Dim BirthDate
FirstName = "John"
…
BirthDate = "1/1/1950"
% >
< !-- #include file="inc1.asp" -- >
/app2/include_1.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時間= 5.93 msec/page
差 = +0.36 msec (6.5% 增加)
這不奇怪。使用Include 文件形成了負(fù)載。
在函數(shù)區(qū)使用Include 文件
在這里,代碼都包裝在一個Include 文件中的子程序里。Include 引用是在頁面頂部進(jìn)行的,在ASP腳本的適當(dāng)位置調(diào)用子程序。
< % OPTION EXPLICIT
Dim FirstName
…
Dim BirthDate
FirstName = "John"
…
BirthDate = "1/1/1950"
CALL writeTable()
% >
< !-- #include file="inc2.asp" -- >
/app2/include_2.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時間= 6.08 msec/page
差 =+0.51 msec (9.2% 增加)
這對性能造成的影響比functions調(diào)用還大。因此:只有當(dāng)代碼在頁面之間共享時才使用Include 文件。
執(zhí)行錯誤處理時會形成多大的負(fù)載?
對于所有真正的應(yīng)用程序來說,錯誤處理都是必要的。這個測試中,通過調(diào)用On Error Resume Next函數(shù)來調(diào)用錯誤句柄。
< % OPTION EXPLICIT
On Error Resume Next
Dim FirstName
…
/app2/error_1.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時間= 5.67 msec/page
差= 0.10 msec (1.8% 增加)
你可以看到,錯誤句柄帶來了代價。我們可以提出以下建議:只有在會發(fā)生超出測試或控制能力之外的情況時才使用錯誤句柄。一個最基本的例子就是使用存取其它資源,如ADO或FileSystem 對象的COM對象。
設(shè)置一個上下文處理是否對性能有影響?
當(dāng)錯誤發(fā)生時,在頁面上設(shè)置一個上下文處理允許腳本進(jìn)行反轉(zhuǎn)操作。這是通過在頁面上使用處理聲明來設(shè)置的。
< %@ TRANSACTION = REQUIRED % >
< % OPTION EXPLICIT
Dim FirstName
…
/app2/transact1.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時間= 13.39 msec/page
差 = +7.82 msec (140.4% 增加)
啊!這真實最具有戲劇性的結(jié)果。所以請留意以下規(guī)則:只有當(dāng)兩個或更多操作被作為一個單元執(zhí)行時,才使用處理上下文。
結(jié)論
本文第一部分的重要之處在于許多小事情的累積。為了強調(diào)這個問題,我設(shè)置了最后一個測試,在其中進(jìn)行了我們以前曾經(jīng)測試過的看來無所謂但實際上有壞影響的所有操作。我包含了許多Response.Write 聲明、關(guān)閉了緩沖器、設(shè)置了默認(rèn)語言、去掉了Option Explicit 引用并初始化了錯誤句柄。
< %@ LANGUAGE=VBSCRIPT % >
< %
On Error Resume Next
FirstName = "John"
…
BirthDate = "1/1/1950"
Response.Write("< html >")
Response.Write("< head >")
Response.Write(" < title >Response Test< /title >")
Response.Write("< /head >")
Response.Write("< body >")
Response.Write("< h1 >Response Test< /h1 >")
Response.Write("< table >")
Response.Write("< tr >< td >< b >First Name:< /b >< /td >< td >" & FirstName & "< /td >< /tr >")
…
Response.Write("< tr >< td >< b >Birth Date:< /b >< /td >< td >" & BirthDate & "< /td >< /tr >")
Response.Write("< /table >")
Response.Write("< /body >")
Response.Write("< /html >")
% >
/app2/final_1.asp片段
基準(zhǔn)值 = 5.57 msec/page
反應(yīng)時間 = 8.85 msec/page
差 = +3.28 msec (58.9% 增加)
聽起來可能很明顯,但是理解更重要,那就是我們放置在頁面上的代碼會對性能有影響。頁面上的小變化有時會大大地增加反應(yīng)時間。
規(guī)則概括
* 避免內(nèi)聯(lián)ASP的過多使用。
* 總是將連續(xù)Response.Write 語句連接進(jìn)一個單獨語句內(nèi)。
* 永遠(yuǎn)不要在Response.Write 周圍使用包裝函數(shù)以附加CRLF。
* 如果必須格式化HTML輸出,直接在Response.Write 語句內(nèi)附加CRLF。
* 總是通過服務(wù)器設(shè)置開啟緩沖器。
* 只要使用適度,ASP注釋對性能的影響很小或根本沒有影響。
* 設(shè)置服務(wù)器的默認(rèn)語言配置以與站點上使用的語言相匹配。
* 除非你使用非默認(rèn)語言,不要設(shè)置語言聲明。
* 在VBScript中總是使用Option explicit 。
* 在不需要的情況下,總是在頁面或應(yīng)用程序的水平上關(guān)閉Session狀態(tài)。
* 只有當(dāng)代碼在頁面之間共享時才使用Include 文件。
* 在一個頁面上,如果代碼要使用一次以上,就將代碼封入函數(shù)區(qū)。
* 適當(dāng)時候,將變量聲明移到函數(shù)范圍內(nèi)。
* 只有會發(fā)生超出測試或控制能力之外的情況時才使用錯誤句柄。
* 只有當(dāng)兩個或更多操作被作為一個單元執(zhí)行時,才使用上下文處理。
現(xiàn)在回顧一下,有許多問題可以作為普遍性的方針:
* 避免冗余--不要設(shè)置那些默認(rèn)狀態(tài)下已經(jīng)設(shè)置的屬性。
* 限制函數(shù)調(diào)用的次數(shù)。
* 縮小代碼的范圍。
在本文的第二部分,我們將探索有關(guān)ADO和COM對象一些深入的問題。
在本文的第一部分中,我回顧了有關(guān)ASP開發(fā)的一些基本問題,介紹了一些性能測試的結(jié)果,以理解我們放置在頁面中的代碼可能對運行性能造成什么樣的影響。在這個系列的第二部分,我們將探討經(jīng)過論證的ASP最廣泛的用途,即通過ActiveX 數(shù)據(jù)對象(ADO)交互使用數(shù)據(jù)庫內(nèi)容。ADO是Microsoft通用并簡單的數(shù)據(jù)庫界面。
ADO有很多的功能設(shè)置,因此準(zhǔn)備這篇文章時最大的挑戰(zhàn)便是限制測試問題的范圍?紤]到讀取大數(shù)據(jù)集會為web 服務(wù)器施加很大的負(fù)載,我決定將研究的內(nèi)容局限在為使用ADO記錄集尋找最優(yōu)化配置的方面。但是這個限制還是提出了一個挑戰(zhàn),因為ADO為執(zhí)行同一個功能提供了多種方式。比如說,記錄集可以從Recordset 類中恢復(fù),也可以從Connection和Command 類中恢復(fù)。另外,一旦你有了一個記錄集,那么有很多個選擇會戲劇性地影響性能。因此,同第一部分一樣,我將盡可能地多涉及一些具體問題。
目的
我研究的目的是獲取足夠的信息以找到以下問題的答案:
* 是否應(yīng)該使用ADOVBS.inc包含文件?
* 當(dāng)使用一個記錄集時,是否應(yīng)該創(chuàng)建一個單獨的Connection對象?
* 恢復(fù)一個記錄集最好的方法是什么?
* 指針和鎖的類型中,哪些是最有效的?
* 是否應(yīng)該使用斷開的記錄集?
* 設(shè)置記錄集(Recordset)屬性的最好方法是什么?
* 引用記錄集中域值的最有效方法是什么?
* 使用臨時字符串可以較好地代替緩沖器嗎?
測試是如何設(shè)立的?
為進(jìn)行這項研究中的測試,我們共組裝了21個ASP頁面(包含在本文下載內(nèi)容中)。每個頁面都被配置成用3個不同的查詢返回記錄集運行,這些記錄集中分別有0、25、250條記錄。這可以幫助我們將裝載記錄集的問題和在記錄集中循環(huán)上的性能問題隔離開。
為滿足這些變化的條件,數(shù)據(jù)庫連接字符串和測試SQL字符串都作為應(yīng)用程序變量存儲在Global.asa中。因為我們的測試數(shù)據(jù)庫是在Microsoft SQL Server 7.0上運行的,因此我們的連接字符串指定OLEDB作為連接供應(yīng)者、Northwind 樣本數(shù)據(jù)庫(包含在SQL服務(wù)器中)作為當(dāng)前數(shù)據(jù)庫。SQL SELECT語句要求Northwind Orders 表格中的7個特定域。
< SCRIPT LANGUAGE=VBScript RUNAT=Server >
Sub Application_OnStart
Application("Conn") = "Provider=SQLOLEDB; " & _
"Server=MyServer; " & _
"uid=sa; " & _
"pwd=;" & _
"DATABASE=northwind"
Application("SQL") = "SELECT TOP 0 OrderID, " & _
" CustomerID, " & _
" EmployeeID, " & _
" OrderDate, " & _
" RequiredDate, " & _
" ShippedDate, " & _
" Freight " & _
"FROM [Orders] "
End Sub
< /SCRIPT >
'alternate sql ?25 records
Application("SQL") = "SELECT TOP 25 OrderID, " & _
" CustomerID, " & _
" EmployeeID, " & _
" OrderDate, " & _
" RequiredDate, " & _
" ShippedDate, " & _
" Freight " & _
"FROM [Orders] "
'alternate sql ?250 records
Application("SQL") = "SELECT TOP 250 OrderID, " & _
" CustomerID, " & _
" EmployeeID, " & _
" OrderDate, " & _
" RequiredDate, " & _
" ShippedDate, " & _
" Freight " & _
"FROM [Orders] "
我們的測試服務(wù)器是一個雙450 MHz Pentium ,512MB的RAM,在其上運行著NT Server 4.0 SP5, MDAC 2.1 (數(shù)據(jù)訪問組件)以及Microsoft Scripting Engine的5.0版本。SQL服務(wù)器在一個同樣規(guī)格的單獨機器上運行。同第一篇文章一樣,我使用Microsoft的Web應(yīng)用程序重點工具記錄從最初的頁面請求到傳輸最后一個字節(jié)(TTLB )的時間,精確到服務(wù)器上的毫秒級。這個測試腳本運行20小時,調(diào)用每個頁面1300次以上。顯示的時間是session的平均TTLB。要記住的是,同第一篇文章一樣,我們只是試圖涉及性能方面的問題,而非伸縮性和容量的問題。
還請注意,我們在服務(wù)器上開啟了緩沖器。另外,我把所有的文件名都定為同樣長度,因此文件名中就會有一個或多個下劃線來襯墊。
開始
在第一個測試中,我們使用典型Microsoft ASP ADO 樣本文件中的典型場景來恢復(fù)一個簡單的記錄集。在這個例子( ADO__01.asp )中,我們首先創(chuàng)建一個Connection對象,然后創(chuàng)建一個Recordset對象。當(dāng)然,我在腳本中進(jìn)行了一些修改,以反映在本系列的第一部分中涉及到的一些好的做法。
< % Option Explicit % >
< !-- #Include file="ADOVBS.INC" -- >
< %
Dim objConn
Dim objRS
Response.Write( _
"< HTML >< HEAD >" & _
"< TITLE >ADO Test< /TITLE >" & _
"< /HEAD >< BODY >" _
)
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = adOpenForwardOnly
objRS.LockType = adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
Response.Write( _
"< TABLE BORDER=1 >" & _
"< TR >" & _
"< TH >OrderID< /TH >" & _
"< TH >CustomerID< /TH >" & _
"< TH >EmployeeID< /TH >" & _
"< TH >OrderDate< /TH >" & _
"< TH >RequiredDate< /TH >" & _
"< TH >ShippedDate< /TH >" & _
"< TH >Freight< /TH >" & _
"< /TR >" _
)
'write data
Do While Not objRS.EOF
Response.Write( _
"< TR >" & _
"< TD >" & objRS("OrderID") & "< /TD >" & _
"< TD >" & objRS("CustomerID") & "< /TD >" & _
"< TD >" & objRS("EmployeeID") & "< /TD >" & _
"< TD >" & objRS("OrderDate") & "< /TD >" & _
"< TD >" & objRS("RequiredDate") & "< /TD >" & _
"< TD >" & objRS("ShippedDate") & "< /TD >" & _
"< TD >" & objRS("Freight") & "< /TD >" & _
"< /TR > " _
)
objRS.MoveNext
Loop
Response.Write("< /TABLE >")
End If
objRS.Close
objConn.Close
Set objRS = Nothing
Set objConn = Nothing
Response.Write("< /BODY >< /HTML >")
% >
結(jié)果是這樣的:
現(xiàn)在先來看看每一欄中的數(shù)字代表什么:
0 代表運行返回0個記錄的查詢時的TTLB,單位毫秒。在我們所有測試中,這個數(shù)字用來標(biāo)志頁面的負(fù)載或裝載頁面創(chuàng)建對象但不在數(shù)據(jù)中循環(huán)所用的時間。
25 裝載并顯示25條記錄的TTLB(毫秒)。
tot time/25 TTLB除以25條記錄(毫秒)。代表每條記錄的總平均時間。
disp time/25 以毫秒計的TTLB減去“0”那欄的TTLB,并除以25條記錄。代表在記錄集中循環(huán)顯示每條記錄的時間。
250 裝載并顯示250條記錄的TTLB(毫秒)。
tot time/250 TTLB除以250條記錄(毫秒)。代表每條記錄的總平均時間。
disp time/250 以毫秒計的TTLB減去“0”那欄的TTLB,并除以250條記錄。代表在記錄集中循環(huán)顯示每條記錄的時間。
我們將用下面測試的結(jié)果與這些值相比較。
是否應(yīng)該使用ADOVBS.inc 包含文件?
這個問題我想快點解決。Microsoft 提供的ADOVBS.inc 文件包含270行代碼,代表可以應(yīng)用于ADO屬性的大部分常量。我們的例子中只引用了這個文件中的2個常量。因此對于這個測試( ADO__02.asp ),我取消了包含文件的引用,并用屬性列舉中的實際數(shù)字代替了常量。
objRS.CursorType = 0 ' adOpenForwardOnly
objRS.LockType = 1 ' adLockReadOnly
我們可以看到裝載時間減少了23%。這與每條記錄的顯示時間有定義上的不同,因為這種改變對于在記錄集中循環(huán)不應(yīng)該有影響。這個問題有幾種解決辦法。我建議使用ADOVBS.inc 文件作為參考,必要時使用注釋來注明數(shù)字。要記住,就如同在第一部分所闡明的一樣,注釋是不需要懼怕的,因為只要使用適度,它們不會給性能帶來大的影響。另一種方法是只從文件中將你所需要的常量復(fù)制到頁面中。
解決這個問題有一個很酷的方法,通過將ADO類庫連接到你的應(yīng)用程序,使所有的ADO常量都可用。將以下代碼增加到你的Global.asa 文件,你就可以直接使用所有的常量。
< !--METADATA TYPE="typelib"
FILE="C:\Program Files\Common Files\SYSTEM\ADO\msado15.dll"
NAME="ADODB Type Library" -- >
或
< !--METADATA TYPE="typelib"
UUID="00000205-0000-0010-8000-00AA006D2EA4"
NAME="ADODB Type Library" -- >
所以,這里是我們的第一個規(guī)則:
* 避免包含ADOVBS.inc文件,用其它方法來使用常量。
當(dāng)使用一個記錄集時,是否應(yīng)該創(chuàng)建一個單獨的Connection對象?
要想正確回答這個問題,需要在兩個不同情況下檢驗測試結(jié)果:第一是每頁執(zhí)行一個數(shù)據(jù)庫處理的情況,第二是每頁執(zhí)行多個數(shù)據(jù)庫處理的情況。
在前面的例子中,我們已經(jīng)創(chuàng)建了一個單獨的Connection對象,并將它傳遞到記錄集的ActiveConnection 屬性。但是也有可能僅僅把連接字符串傳遞到這個屬性中,從而可以避免一個額外的步驟,即在腳本( ADO__03.asp )中例示和配置一個單獨的組件:
objRS.ActiveConnection = Application("Conn")
盡管我們?nèi)匀辉谟涗浖袆?chuàng)建了一個連接,但它是在非常優(yōu)化的情況下創(chuàng)建的,所以剛一開始我們就看到啟動時間比以前的測試減少了23%,同預(yù)料中一樣,同每個記錄的顯示時間幾乎沒有什么差別。
因此,我們的第二個規(guī)則是:
* 當(dāng)使用一個單個記錄集時,將連接字符串傳遞到ActiveConnection屬性中。
下面要確定當(dāng)在一個頁面上創(chuàng)建多個記錄集時,這個邏輯是否依然成立。為測試這個情況,我引入了FOR 循環(huán),將前面的例子重復(fù)10次。在這個測試中,我們還將研究3種選擇:
第一,我們在每個循環(huán)中創(chuàng)建并銷毀Connection 對象( ADO__04.asp ):
Dim i
For i = 1 to 10
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
objConn.Close
Set objConn = Nothing
Next
第二,在循環(huán)外創(chuàng)建一個單獨的Connection 對象,并與每個記錄集共享它( ADO__05.asp ):
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Dim i
For i = 1 to 10
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
Next
objConn.Close
Set objConn = Nothing
第三,在每個循環(huán)中將連接字符串傳遞到ActiveConnection 屬性( ADO__06.asp ):
Dim i
For i = 1 to 10
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = Application("Conn")
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
Next
你可能已經(jīng)猜到了,在每個循環(huán)中創(chuàng)建并銷毀Connection 對象是一個低效率的方法。但是令人吃驚的是,僅僅在每個循環(huán)中傳遞連接字符串比共享單一連接對象的效率只低一點點。
盡管如此,我們的第3條規(guī)則是:
* 在一個頁面上使用多個記錄集時,創(chuàng)建一個Connection 對象,在ActiveConnection 屬性中重復(fù)使用它。
指針和鎖的類型中,哪些是最有效的?
到目前為止,我們所有測試都只用了只向前(Forward Only )的指針在記錄集中循環(huán)。但是,ADO還為記錄集提供了3種類型的指針:Static(靜態(tài)), Dynamic(動態(tài))和 Keyset(鍵盤)。每一種都提供了額外的功能,比如向前和向后移動以及當(dāng)別人建立數(shù)據(jù)時可以看到修改情況的功能。不過,討論這些指針類型的內(nèi)涵不是本文討論的范圍。我把這些留給你自己。下面是各種類型的比較分析。
與它們的同類Forward Only 相比,這些額外的指針都明顯地造成了更大的負(fù)載( ADO__03.asp )。另外這些指針在循環(huán)期間也更慢。我想與你一起分享的一條忠告是要避免這種想法:“我不時地需要一下Dynamic 指針,所以干脆總是用它算了!
從本質(zhì)上說,同樣的問題也適用于鎖的類型。前面的測試中只使用了Read Only(只讀)類型的鎖。但是,還有三種類型的鎖:Lock Pessimistic、 Lock Optimistic和Lock Batch Optimistic。同指針的選擇一樣,這些鎖也為處理記錄集中的數(shù)據(jù)提供了額外的功能和控制。同樣,我將學(xué)習(xí)每種鎖設(shè)置的適當(dāng)用途的內(nèi)容留給你自己。
所以引導(dǎo)我們考慮規(guī)則4的邏輯很簡單:使用最適合你的任務(wù)的最簡單的指針和鎖的類型。
獲取一個記錄集最好的方式是什么?
到目前為止,我們只是通過Recordset 對象來恢復(fù)記錄集。但是ADO還提供了一些獲取記錄集的間接方法。下一個測試就將ADO__03.asp 中的值與直接從一個Connection對象中創(chuàng)建一個記錄集對象( CONN_01.asp )來比較。
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = objConn.Execute(Application("SQL"))
我們看到,負(fù)載有一個輕微的增加,顯示每條記錄的時間沒有變化。
然后,我們看看從一個Command 對象中直接創(chuàng)建一個Recordset 對象( CMD__01.asp ):
Set objCmd = Server.CreateObject("ADODB.Command")
objCmd.ActiveConnection = Application("Conn")
objCmd.CommandText = Application("SQL")
Set objRS = objCmd.Execute
我們再次看到負(fù)載有一個輕微的增加,每個記錄的顯示時間有一個名義上的區(qū)別。雖然最后這兩種方法對性能的影響很小,卻有一個大問題需要考慮。
通過Recordset 類創(chuàng)建一個記錄集對于控制如何處理記錄集提供了最大的靈活性。雖然其它方法也沒有提出一個壓倒性的性能問題,但是你會被默認(rèn)狀態(tài)下返回何種指針類型和鎖類型而困惑,這些對于你的特定需求來說不一定是最優(yōu)的。
所以,除非因為某種特殊原因你需要其它方法的話,請遵循第5條規(guī)則:通過ADODB.Recordset 類例示記錄集以獲得最好的性能和最大的靈活性。
是否應(yīng)該斷開記錄集?
ADO為斷開一個記錄集提供了一種選擇,記錄集要在一個向前查詢中恢復(fù)所有數(shù)據(jù)、關(guān)閉連接、使用一個本地(或客戶)指針在數(shù)據(jù)集中移動。這還提供了一個早期釋放連接的機會。這種情況對于處理遠(yuǎn)程數(shù)據(jù)服務(wù)是必要的,因為這種情況下數(shù)據(jù)必須從數(shù)據(jù)庫斷開。但是對于普通的用途,這樣做有好處嗎?
下面我們增加了CursorLocation 屬性,打開記錄集后關(guān)閉連接( CLIENT1.asp ):
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.CursorLocation = 3 ' adUseClient
objRS.ActiveConnection = Application("Conn")
objRS.LockType = 1 ' adLockReadOnly
objRS.Open Application("SQL")
objRS.ActiveConnection = Nothing
從理論上說,這個技術(shù)應(yīng)該導(dǎo)致性能更快。原因有兩個:首先,在記錄集中移動時,避免了通過連接的重復(fù)請求;其次通過較早地取消連接減輕了資源需求。但是,在使用客戶端指針時,效率低得很明顯。可能是由于當(dāng)使用客戶指針位置時,不管你的設(shè)置是什么,CursorType 都被修改成Static。
規(guī)則6是這樣的:除非是一個斷開的環(huán)境中所要求的,避免使用斷開的記錄集。
什么是設(shè)置記錄集屬性的最好方法?
前面所有的測試都是通過單獨的屬性設(shè)置來直接設(shè)置記錄集的屬性的。但是Recordset.Open 函數(shù)可以為我們所需要的全部屬性接收額外的參數(shù)。雖然對于每個屬性來說,單獨的代碼行易于閱讀和維護(hù),它們還是要分別執(zhí)行一個單獨函數(shù)調(diào)用,必須通過COM界面來集合( ADO__07.asp ):
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.Open Application("SQL"), Application("Conn"), 0, 1
' adForwardOnly, adLockReadOnly
這些方法在負(fù)載上帶來得差別小得驚人,于是我們得到規(guī)則7:不要對單獨設(shè)置記錄集屬性感到擔(dān)心
引用記錄集中域值的最有效方法是什么?
到目前為止,我都是用名字引用記錄集中的域值的。這可能是一種效率很低的方法,因為每次調(diào)用都需要查找域。為了證明這一點,下面的測試就要通過記錄集中域的集合的指針來引用域(ADO__08.asp):
'write data
Do While Not objRS.EOF
Response.Write( _
"< TR >" & _
"< TD >" & objRS(0) & "< /TD >" & _
"< TD >" & objRS(1) & "< /TD >" & _
"< TD >" & objRS(2) & "< /TD >" & _
"< TD >" & objRS(3) & "< /TD >" & _
"< TD >" & objRS(4) & "< /TD >" & _
"< TD >" & objRS(5) & "< /TD >" & _
"< TD >" & objRS(6) & "< /TD >" & _
"< /TR > " _
)
objRS.MoveNext
Loop
正如我們所預(yù)料的,裝載時間的變化很。ú町惪赡苁怯捎诖a上的輕微減少引起的)。但是這種技術(shù)在有效顯示時間上卻帶來了明顯的減少。
在下面的例子中,我們將給每個域指定一個單獨的變量。這種方法避免了在表格循環(huán)內(nèi)的所有查找( ADO__09.asp ):
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
Dim fld0
Dim fld1
Dim fld2
Dim fld3
Dim fld4
Dim fld5
Dim fld6
Set fld0 = objRS(0)
Set fld1 = objRS(1)
Set fld2 = objRS(2)
Set fld3 = objRS(3)
Set fld4 = objRS(4)
Set fld5 = objRS(5)
Set fld6 = objRS(6)
'write data
Do While Not objRS.EOF
Response.Write( _
"< TR >" & _
"< TD >" & fld0 & "< /TD >" & _
"< TD >" & fld1 & "< /TD >" & _
"< TD >" & fld2 & "< /TD >" & _
"< TD >" & fld3 & "< /TD >" & _
"< TD >" & fld4 & "< /TD >" & _
"< TD >" & fld5 & "< /TD >" & _
"< TD >" & fld6 & "< /TD >" & _
"< /TR >" _
)
objRS.MoveNext
Loop
Set fld0 = Nothing
Set fld1 = Nothing
Set fld2 = Nothing
Set fld3 = Nothing
Set fld4 = Nothing
Set fld5 = Nothing
Set fld6 = Nothing
Response.Write("< /TABLE >")
End If
到目前,這種方法形成的結(jié)果是最好的。每條記錄的顯示時間下降成了.45 毫秒。
現(xiàn)在,所有測試腳本的配置都要求對結(jié)果記錄集有一些了解。比如說,我們一直在欄標(biāo)題中給域名編碼,單獨地引用這些域的值。下面的例子提供了一個動態(tài)的解決方案,在域的集合中循環(huán),不僅得到數(shù)據(jù),也得到域的標(biāo)題(ADO__10.asp ):
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
Response.Write("< TABLE BORDER=1 >< TR >")
For Each objFld in objRS.Fields
Response.Write("< TH >" & objFld.name & "< /TH >")
Next
Response.Write("< /TR >")
'write data
Do While Not objRS.EOF
Response.Write("< TR >")
For Each objFld in objRS.Fields
Response.Write("< TD >" & objFld.value & "< /TD >")
Next
Response.Write("< /TR >")
objRS.MoveNext
Loop
Response.Write("< /TABLE >")
End If
可以看到,我們在性能上有一個損失,但是這個方法還是比ADO__07.asp要快一些。
下面的測試是在最后兩個測試之間進(jìn)行一些折中。通過在一個動態(tài)分配數(shù)組中保存域的引用,既維持了動態(tài)的靈活性,也挽回了一些性能上的損失。
If objRS.EOF Then
Response.Write("No Records Found")
Else
Dim fldCount
fldCount = objRS.Fields.Count
Dim fld()
ReDim fld(fldCount)
Dim i
For i = 0 to fldCount-1
Set fld(i) = objRS(i)
Next
'write headings
Response.Write("< TABLE BORDER=1 >< TR >")
For i = 0 to fldCount-1
Response.Write("< TH >" & fld(i).name & "< /TH >")
Next
Response.Write("< /TR >")
'write data
Do While Not objRS.EOF
Response.Write("< TR >")
For i = 0 to fldCount-1
Response.Write("< TD >" & fld(i) & "< /TD >")
Next
Response.Write("< /TR >")
objRS.MoveNext
Loop
For i = 0 to fldCount-1
Set fld(i) = Nothing
Next
Response.Write("< /TABLE >")
End If
雖然它并不比最好值快,但是比前面的幾個例子要快了很多,并且有一個優(yōu)勢就是能夠動態(tài)地表現(xiàn)任何記錄集。
在下一個測試中,我們將對以前的方案做一個徹底的改變,使用記錄集的GetRows指令創(chuàng)建一個循環(huán)用的數(shù)組,而不是在記錄集本身進(jìn)行循環(huán)。注意,調(diào)用GetRows之后,立刻就將記錄集設(shè)置為Nothing,這樣就能更快地釋放系統(tǒng)資源。另外還要注意數(shù)組的第一個維數(shù)代表域,第二個維數(shù)代表行 ( ADO__12.asp ):
If objRS.EOF Then
Response.Write("No Records Found")
objRS.Close
Set objRS = Nothing
Else
'write headings
...
'set array
Dim arrRS
arrRS = objRS.GetRows
'close recordset early
objRS.Close
Set objRS = Nothing
'write data
Dim numRows
Dim numFlds
Dim row
Dim fld
numFlds = Ubound(arrRS, 1)
numRows = Ubound(arrRS, 2)
For row= 0 to numRows
Response.Write("< TR >")
For fld = 0 to numFlds
Response.Write("< TD >" & arrRS(fld, row) & "< /TD >")
Next
Response.Write("< /TR >")
Next
Response.Write("< /TABLE >")
End If
通過使用GetRows 指令,就可以獲取整個記錄集并將其裝載到數(shù)組中。當(dāng)恢復(fù)特別大的記錄集時,這種方法有可能會造成資源問題,但是數(shù)據(jù)的循環(huán)快多了,因為類似于MoveNext 的函數(shù)調(diào)用和EOF 的檢測都可以取消了。
不過速度的提升確實是有代價的,因為記錄集的元數(shù)據(jù)不再與數(shù)據(jù)在一起。圍繞這個問題,我在調(diào)用GetRows之前用記錄集來恢復(fù)標(biāo)題名。另外還可以提前提取數(shù)據(jù)類型和其它信息。還要注意,在我們的測試中,性能上的優(yōu)勢只有在使用大一些的記錄集時才能看到。
在這部分最后的測試中,我們更進(jìn)一步,使用記錄集的GetString 指令。這個方法將整個記錄集提取到一個大的字符串中,允許你指定自己的分隔符( ADO__13.asp ):
If objRS.EOF Then
Response.Write("No Records Found")
objRS.Close
Set objRS = Nothing
Else
'write headings
...
'set array
Dim strTable
strTable = objRS.GetString (2, , "< /TD >< TD >", "< /TD >< /TR >< TR >< TD >")
'close recordset early
objRS.Close
Set objRS = Nothing
Response.Write(strTable & "< /TD >< /TR >< /TABLE >")
End If
雖然這種方法已經(jīng)接近了最高水平,但是它只適合于最簡單的設(shè)計,因為它根本就不能應(yīng)用于數(shù)據(jù)的特殊情況。
觀察
在我們開始這套測試之前,執(zhí)行每條記錄的時間一直在.83 毫秒左右震動。這套測試中的大多數(shù)方法都將這個數(shù)字減少了一半。雖然有些方法明顯地提供了更快的速度,但是代價是靈活性的降低。
下面的規(guī)則是以重要程度為順序的:
* 當(dāng)記錄集中的值不需要用一種特殊方式來對待并且能夠格式化為一種統(tǒng)一的格式時,使用GetString方法來提取數(shù)據(jù)。
* 當(dāng)你在設(shè)計上需要更大的靈活性,但是又不需要用記錄集的元數(shù)據(jù)進(jìn)行工作,使用GetRows 方法將數(shù)據(jù)提取到一個數(shù)組中。
* 當(dāng)你需要設(shè)計的靈活性和元數(shù)據(jù)時,在進(jìn)入一個數(shù)據(jù)恢復(fù)的循環(huán)之前,將你的域約束在本地變量中。避免用名字引用域。
使用臨時字符串可以較好地代替緩沖器嗎?
這是針對我上一篇文章提交的一些注解所引發(fā)的一個小小的離題。要討論的問題是圍繞著緩沖器的使用及使用臨時字符串作為替代來收集輸出,這樣就允許Response.Write 只調(diào)用一次。為了測試,我從ADO_11.asp的代碼開始,將結(jié)果附加到一個字符串中,而不是在每個循環(huán)都調(diào)用Response.Write,當(dāng)整個操作都結(jié)束后,在字符串上調(diào)用Response.Write ( STR__01.asp ):
Dim strTable
strTable = ""
'write headings
strTable = strTable & "< TABLE BORDER=1 >< TR >"
For i = 0 to fldCount-1
strTable = strTable & "< TH >" & fld(i).name & "< /TH >"
Next
strTable = strTable & "< /TR >"
'write data
Do While Not objRS.EOF
strTable = strTable & "< TR >"
For i = 0 to fldCount-1
strTable = strTable & "< TD >" & fld(i) & "< /TD >"
Next
strTable = strTable & "< /TR >"
objRS.MoveNext
Loop
For i = 0 to fldCount-1
Set fld(i) = Nothing
Next
strTable = strTable & "< /TABLE >"
Response.Write(strTable)
看起來執(zhí)行得不是很好。也許正象許多人建議的,我們應(yīng)該用Space 指令為這個字符串指定一些空間,這樣它就不需要在循環(huán)期間總是為自己重新分配空間( STR__02.asp ):
Dim strTable
strTable = Space(10000)
也許Space 指令并不象建議的那樣工作。我們最后的規(guī)則是:不要用臨時字符串來收集輸出。
規(guī)則的總結(jié)
現(xiàn)在我們來重新總結(jié)一下這些規(guī)則:
* 避免包含ADOVBS.inc文件,用其它方法來使用常量。
* 當(dāng)使用一個單個記錄集時,將連接字符串傳遞到ActiveConnection屬性中。
* 在一個頁面上使用多個記錄集時,創(chuàng)建一個Connection 對象,在ActiveConnection 屬性中重復(fù)使用它。
* 使用最適合你的任務(wù)的最簡單的指針和鎖的類型。
* 通過ADODB.Recordset 類例示記錄集以獲得最好的性能和最大的靈活性。
* 除非是一個斷開的環(huán)境中所要求的,避免使用斷開的記錄集。
* 不要對單獨設(shè)置記錄集屬性感到擔(dān)心。
* 當(dāng)記錄集中的值不需要用一種特殊方式來對待并且能夠格式化為一種統(tǒng)一的格式時,使用GetString方法來提取數(shù)據(jù)。
* 當(dāng)你在設(shè)計上需要更大的靈活性,但是又不需要用記錄集的元數(shù)據(jù)進(jìn)行工作,使用GetRows方法將數(shù)據(jù)提取到一個數(shù)組中。
* 當(dāng)你需要設(shè)計的靈活性和元數(shù)據(jù)時,在進(jìn)入一個數(shù)據(jù)恢復(fù)的循環(huán)之前,將你的域約束在本地變量中。避免用名字引用域。
* 不要用臨時字符串來收集輸出。
結(jié)論
同樣,從這些測試中我們所學(xué)到的最重要的一點是:小小的變化會在性能上造成很大的影響。如果我們把第一個測試與ADO__09.asp(在記錄集中循環(huán)的最快結(jié)果)相比,可以看到在反應(yīng)時?br>
所以要記住,永遠(yuǎn)不要想當(dāng)然。如果你不能肯定,那就運行一些有針對性的測試。