设计之家 > 教程 > 網頁教程 > 其他教程 >

22個提高ASP性能的技巧(1-12)

作者:未知 來源:ARTDESIGN 時間:2005-04-23
22個提高ASP性能的技巧(1-12)
作者:未知


介紹
對于ASP程序來說,性能非常重要;必須在最初設計程序時就要考慮到性能,否則以后恐怕就要因為性能問題而重寫代碼。怎樣才能使ASP程序性能達到最大呢?本文就將介紹一些提高ASP程序性能的技巧。

技巧1:將常用數據在Web服務器端緩存起來
大部分的ASP頁面都要從后臺數據庫中提取數據,然后將數據用HTML方式表現出來。
不管你的數據庫多么快,從內存中提取數據總比從后臺數據庫中提取快;從本地硬盤中讀取數據通常也比從數據庫中快。因此,你可以通過在Web服務器端緩存數據來提高性能。

緩存是個典型的以空間換取時間的交易。如果你正確的緩存了數據,性能可能會突飛猛進。要想一個緩存能真正發揮效益,必須緩沖那些常用和計算復雜的數據。裝滿過期數據的緩沖區只能浪費內存。

不經常變化的數據也是緩存的一個良好候選者,因為你可以不用關心同數據庫中的數據保持同步。下拉列表框、引用表、小段DHTML代碼,XML字符串、菜單項和站點配置變量(包括數據源名字(DSN),IP地址和Web路徑)都是很好的緩存候選者。注意,不僅僅可以緩存數據本身,還可以緩存數據的表現。如果一個ASP頁面很少變化,并且緩存代價比較高(比如,產品列表),可以考慮用靜態HTML頁面。

技巧2:用Application對象或Session對象緩存常用數據
ASP的Application和Session對象是一個極其方便的在內存中緩存數據的容器。你可以把數據放到Application或Session對象中,這些數據就會在整個HTTP調用中一直存在。每個用戶有自己的Session對象中的數據,而Application對象中的數據可以在所有用戶中共享。

應該在什么時候將數據裝入Application或Session中呢?通常,數據在Application或Session啟動的時候裝入。要想在Application或Session啟動的時候裝入數據,需要分別在Global.asa的Application_OnStart()或Session_OnStart()中添加適當的代碼;如果Global.asa中沒有這兩個函數,你可以手工添加。也可以在數據第一次使用的時候將其裝入。要想這樣,應該在ASP頁面中寫一些代碼(或是寫一個可重用的腳本函數)來檢查數據是否存在并且如果數據不存在則將其裝入內存。下面是一個經典的性能調整技術--Lazy Evaluation:

<%
Function GetEmploymentStatusList
 Dim d
 d = Application("EmploymentStatusList")
 If d = "" Then
   ' FetchEmploymentStatusList function (not shown)
   ' fetches data from DB, returns an Array
   d = FetchEmploymentStatusList()
   Application("EmploymentStatusList") = d
 End If
 GetEmploymentStatusList = d
End Function
%>

Similar functions could be written for each chunk of data needed.

In what format should the data be stored? Any variant type can be
stored, since all script variables are variants. For instance, you
can store strings, integers, or arrays. Often, you’ll be storing the
contents of an ADO recordset in one of these variable types. To get
data out of an ADO recordset, you can manually copy the data into
VBScript variables, one field at a time. It’s faster and easier to
use one of the ADO recordset persistence functions GetRows(),GetString
() or Save() (ADO 2.5). Full details are beyond the scope of this
article, but here’s a function that demonstrates using GetRows() to
return an array of recordset data:

' 獲取記錄集,返回數組
Function FetchEmploymentStatusList
 Dim rs
 Set rs = CreateObject("ADODB.Recordset")
 rs.Open "select StatusName, StatusID from EmployeeStatus", _
     "dsn=employees;uid=sa;pwd=;"
 FetchEmploymentStatusList = rs.GetRows() ' 將記錄集用數組返回
 rs.Close
 Set rs = Nothing
End Function

A further refinement of the above might be to cache the HTML for the
list, rather than the array. Here’s a simple sample:

' 獲取記錄集,返回HTML Option列表
Function FetchEmploymentStatusList
 Dim rs, fldName, s
 Set rs = CreateObject("ADODB.Recordset")
 rs.Open "select StatusName, StatusID from EmployeeStatus", _
     "dsn=employees;uid=sa;pwd=;"
 s = "<select name=""EmploymentStatus">" & vbCrLf
 Set fldName = rs.Fields("StatusName") ' ADO 字段綁定
 Do Until rs.EOF
  s = s & " <option>" & fldName & "</option>" & vbCrLf
  rs.MoveNext
 Loop
 s = s & "</select>" & vbCrLf
 rs.Close
 Set rs = Nothing ' 釋放rs
 FetchEmploymentStatusList = s ' 用字符串方式返回數據
End Function

在正確情況下,你可以將ADO記錄集本身緩存在Application或Session范圍,但必須滿足下面兩個條件: .ADO必須被標記為自由線程模型(Free-threaded) .必須使用無連接記錄集

如果不能滿足上面兩個條件,一定不要緩存記錄集。在下面的“不靈活的組件”和“不要緩存Connection”兩個技巧中,我們將討論在Application和Session中保存COM對象的危險性。

當你在Application或Session中存儲數據后,數據將一直保存,知道你的程序改變它,或是Session過期,或是Web服務重新啟動。What if the data needs to be updated?手工刷新Application數據,可以調用只有管理員才可訪問的用來刷新數據的ASP頁面;或者定期的通過一個函數來周期性的更新數據。下面的例子在緩存數據中保存了一個時間戳,然后一段時間之后自動刷新數據。

<%
Const UPDATE_INTERVAL = 300 ' 刷新間隔,單位是秒

'返回雇員狀態列表
Function GetEmploymentStatusList
 UpdateEmploymentStatus
 GetEmploymentStatusList = Application("EmploymentStatusList")
End Function

'周期性的更新緩存中的數據
Sub UpdateEmploymentStatusList
 Dim d, strLastUpdate
 strLastUpdate = Application("LastUpdate")
 If (strLastUpdate = "") Or _
    (UPDATE_INTERVAL < DateDiff("s", strLastUpdate, Now)) Then

   ' Note: two or more calls might get in here. This is okay and
will simply
   ' result in a few unnecessary fetches (there is a workaround
for this)

   ' FetchEmploymentStatusList function (not shown)
   ' fetches data from DB, returns an Array
   d = FetchEmploymentStatusList()

   ' 更新Application對象時用Application.Lock()來保持數據一致性
   Application.Lock
   Application("EmploymentStatusList") = Events
   Application("LastUpdate") = CStr(Now)
   Application.Unlock
 End If
End Sub

要知道在Session或Application中緩存大數組并不是一個太好的方法。在訪問數組中的任何元素之前,腳本解釋器都需要生成一個臨時的整個數組的副本。例如,如果你緩存了一個100,000個字符串元素的數組,用來將郵政編碼和當地的天氣對應一一起來,在訪問數組中任何一個字符串之前,ASP解釋器首先必須復制所有的100,000個天氣情況數據到一個臨時數組中。在這種情況下,開發一個組件來儲存天氣情況數據或是使用詞典(Dictioary)對象更為合適一點。不過,也不要因小失大,數組對象的的查找速度更快。索引一個詞典比索引一個數組慢。你可以因你的情況而宜,選擇合適的數據結構。

技巧3:在硬盤上緩存數據和HTML頁面
有時,可能有太多的數據緩存在內存中。“太多”是個模糊的說法,它取決與Web服務器的內存大小、緩存項的數目和這些緩存項被訪問的頻度。無論如何,如果太多的數據在內存中緩存,可以考慮將數據用文本或XML文件緩存到Web服務器的硬盤上。可以將緩存到硬盤上和到內存中結合起來,針對你的站點,找到最優化的策略。

注意,當我們測量單一ASP頁面的性能時,從硬盤上讀取數據可能比從數據庫中讀取慢。但是,緩存能夠減少數據庫和網絡的負載。在高負載的情況下,這將大大提高總體吞吐量。當被緩存的數據是非常復雜的查詢,比如多表連接或是一個復雜的查詢過程或一個非常大的記錄集,緩存的效果將非常明顯。

ASP和COM提供了一些工具來建立基于硬盤的緩存方案。ADO Recordset對象的Save和Open方法可以保存和裝入到磁盤上。還有一些用來訪問文件的組件: .Scripting.FileSystemObject允許你創建、讀取和寫入文件。 .MSXML,同IE捆綁的微軟的XML解釋器,支持保存和裝入XML文檔。 .LookupTable對象是一個用來從磁盤裝入簡單列表的非常好的選擇。

最后,將數據表現緩存在硬盤上,比緩存數據本身要好。生成的HTML可以一個.htm或.asp文件保存在硬盤上;超連可以直接指向那些文件。你也可以用一些商業工具,如XBuilder和SQL Server互連網發布特性,來生成和處理HTML文件。另外,也可以用#include將HTML片段包含到ASP文件中;還可以用FileSystemObject來讀取HTML文件。

技巧4:避免在Application或Session對象中緩存COM對象
雖然在Application或Session對象中緩存數據是一個好注意,但緩存COM對象可能帶來嚴重的后果。在Application或Session對象中緩存常用COM對象非常誘人,但非常不幸,很多COM對象,包括那些用VB 6.0或早期版本寫的組件,如果被緩存到Application或Session對象中將會導致嚴重的性能瓶頸。

特別地,所有非Agile的組件被緩存到Session或Application中時,都將產生性能瓶頸。Agile組件是指聚合了Free-threaded marshaler(FTM)并且線程模型是Both
(ThreadingModel=Both),或線程模型是Neutral(Netural新出現在Windows 2000
和COM+中)的組件。下面的組件都是非Agile的:


自由線程模型組件(除非他們聚合了FTM)
Apartment線程模型組件
單線程組件

Configured組件(MTS/COM+庫和服務包/應用)是非Agile的,除非它們是Neutral線程模型的。Apartment線程模型組件和其他非Agile組件最好是工作在頁面范圍內(就是說,他們在一個單一ASP頁面中創建和銷毀)。

在IIS 4.0中,線程模型是Both的組件被看作是Agile的,但在IIS 5.0中,他們不再滿足Agile的條件。組件線程模型必須是Both的,并且聚合了FTM,才被看作Agile的。如果試圖將一個用Server.CreateObject創建的非Agile組件存儲到Application對象中時,IIS 5.0將會拋出一個錯誤。

當ADO組件被標記為自由線程模型時,ADO記錄集對象可以安全地存儲。可以用Makfre15.bat,一般是放在\\Program Files\Common\System\ADO這個文件夾里,將ADO組件標記為自由線程模型。有一點要注意:當用Access作后臺數據庫時,ADO不能被標記為自由線程模型。詞典(Dictionary)組件也是Agile對象。

技巧5:不要緩存數據庫連接
緩存ADO Connection對象是一個不好的策略。如果一個Connection對象被存儲在Application對象中并被所有頁面使用,所有頁面就會爭著使用這個連接。如果Connection對象被存儲在Session對象中,就要為每個用戶創建一個數據庫連接,這就消減了連接池的作用,并且增大了Web服務器和數據庫服務器的壓力。可以用在每個使用ADO的ASP頁創建和釋放ADO對象來替代緩存數據庫連接;因為IIS內建了數據庫連接池,所以這種方法非常有效。

既然有連接的記錄集保存了一個數據庫連接的引用,因此也不應該在Application或Session對象中保存有連接的記錄集。但是,你可以安全的緩存無連接的記錄集,因為它并不包含到數據連接的引用。要想掛斷一個記錄集,可以采取如下兩個步驟:

  Set rs = Server.CreateObject("ADODB.RecordSet")
  rs.CursorLocation = adUseClient ' 第一步

  rs.Open strQuery, strProv

  ' 將記錄集同數據提供者和數據源掛斷
  rs.ActiveConnection = Nothing  '第二步

技巧6:正確地使用Session對象
我們已經提到了在Application和Session中緩存數據的好處,下面我們將說一些Session對象的缺點。在繁忙的站點上使用Session有一些不利的地方。繁忙是指這個站點每秒鐘要處理數以百計的頁面請求或同時連接數以千計的并發用戶。這個技巧對那些必須要水平伸縮的站點--就是說,這些站點用多個服務器來實現負載平衡或容錯--非常重要。對小的站點,如公司內網,Session相對與他消耗的資源來說,還是值得一用的。

ASP自動為每個訪問Web服務器的擁護創建一個Session對象。每個Session大約消耗10K的資源,并使所有的請求都慢了一點。這個Session在超時周期內一直存在,這個周期一般是20分鐘。對于Session來說最大的問題不是性能而是伸縮能力。Session不是跨Web服務器的;一旦一個Session在某個服務器上創建,它的數據都保存在那兒。
這意味著如果你要在多個Web服務器環境中使用Session,你必須設計一套能使用戶總是訪問它的Session對象所在的Web服務器的策略;即將一個用戶粘到一個Web服務器上。如果Web服務器崩潰,因為Session不是永久保存在磁盤上的餓,所以全部“粘”
在其上的用戶的Session狀態都將丟失。實現“粘Session(sticky session)”的策略包括硬件和軟件方案,如Windows 2000 Advanced Server中的Network Load Balancing和Cisco的Local Director。當然,這些方案并不完美,都要損失一些可伸縮性。Application對象也不是跨服務器的,如果你想在多服務器間共享和更新Application數據,你必須使用一個后臺數據庫。但無論如何,只讀Application數據在多服務器環境中還是十分有用的。

絕大多數任務優先(mission-critical)的站點都想在至少兩臺Web服務器上發布--如果沒有比延長正常運行時間更重要的理由的話。因此,在設計階段,你就要實現“粘Session”,或是簡單地避免Session和其他將用戶狀態保存在一個獨立Web服務器上的狀態管理技術。

如果不使用Session,就將它們關閉;可以通過Internet Service Manager(參看ISM文檔)關閉你的應用的Session功能。如果決定使用Session,就要用一些方法將他們對性能的影響減到最小。可以將不需要Session的內容(如幫助窗口等)移到一個的關閉了Session的ASP應用中。如果某個單一頁面不需要Session,可以將下面的語句放在頁面的頂部來禁止Session功能:

<% @EnableSessionState=False %>

使用該語句還有一個原因是Session在幀中會產生一個有趣的問題。ASP保證任何時候一個會話只有一個請求,這就導致如果瀏覽器同時請求多個頁面,同一時刻將只有一個ASP請求能夠訪問Session;這避免了訪問Session對象時產生的多線程問題;但很不幸,一個幀中的多個頁面只能順序的生成,一個接著一個,而不是兵法。用戶可能會為多個幀等待較長時間。所以如果幀中的某個頁面沒有使用Session,就在頁面頂部放置<% @EnableSessionState=False %>語句。


作為使用Session對象的替代,還有很多其他的方法來管理會話狀態。對小規模的狀態(小于4KB),推薦使用Cookies,QueryString變量和隱藏表單變量。對大量數據,如購物信息,一個后臺數據庫可能是很好的選擇。

技巧7:將代碼封裝到COM對象中
如果有很多VBScript或JScript代碼,可以通過將代碼封裝到COM對象中來提高性能。編譯過的代碼通常比解釋代碼運行得快。COM對象可以通過“前期綁定”來訪問其他COM對象,這比腳本使用的“后期綁定”更高效。

下面是將代碼碼封裝到COM對象中的優點(不僅僅是性能):


COM對象可以很好地將商業邏輯同表現邏輯分離
COM對象使代碼可重用
用VB,C或VJ寫的代碼比ASP代碼更易調試

COM對象也有不足,包括開發周期長和需要不同的編程經驗等。有一點尤需注意,封裝少量ASP代碼可能在性能上適得其反;這種情況下,創建和調用COM對象的代價超過了編譯代碼性能上的好處。如何組合ASP代碼和COM組件代碼來產生最佳性能,往往是個令人頭疼的問題。注意,同Windows NT 4.0/IIS 4.0相比,Windows 2000/IIS 5.0在腳本和ADO性能上已經大大提高。

技巧8:對資源晚獲取,早釋放
通常情況下,晚獲取和早釋放資源是最好的。這不僅適用于COM對象,也適用于文件句柄和其他資源。ADO連接和記錄集是這項優化策略的主要對象。當使用完一個Recordset對象,應該立即將它釋放,而不應等到頁面結束。將VBScript變量設成Nothing是最好的方法。同時,釋放相關的Command和Connection對象(別忘了在將Connection對象設成Nothing之前調用Close()方法)。

技巧9:進程外執行以性能換取可靠性
ASP和MTS/COM+都有選項讓你來用可靠性換取性能。當建立和發布你的應用時,你應該理解這項交易的內幕。

ASP選項
ASP應用有三種運行方法可選擇。在IIS 5.0中,引入“分離級別(isolation
level)”這個術語來描述這些選項。三種分離級別分別是:低(Low),中
(Medium)和高(High)。

低分離級 這種級別被所有版本的IIS支持,并且速度也是最快的。它在
Inetinfo.exe--主要的IIS進程--中運行ASP。如果ASP應用崩潰,IIS也將崩潰。
(在IIS 4.0中,網管必須用諸如InetMon之類的工具來監視IIS,一旦IIS停止,運行批處理文件。IIS 5.0引入了“可靠的重啟(reliable restart)”,會自動重新啟動失敗的服務器。
中分離級 從IIS 5.0開始引入的新級別,指進程外運行,即ASP運行在IIS進程之外。
在中分離級中,所有的ASP應用共享一個進程空間。把多個進程外應用在同個空間中運行,減少了進程的樹木。中分離級是IIS 5.0的默認級別。
高分離級 IIS 4.0和IIS 5.0都支持。高分離級也是進程外的。如果ASP崩潰了,Web服務器并不崩潰。ASP應用會在下個ASP請求到達的時候自動重啟。每個被配置為高分離級的ASP應用有自己的進程空間;這將每個ASP應用保護起來。它的缺點是對每個ASP應用需要一個分離的進程;這增加了許多資源消耗。
哪種選項是最好的。在IIS 4.0中,進程外運行將使性能急劇下降;在IIS 5.0中,許多改進使進程外ASP應用的代價降到最低。事實上,在許多測試中,IIS 5.0中的ASP進程外應用比II4 4.0中的進程內運行都快。但無論如何,在任何平臺上,還是進程內(低分離級)運行能帶來最佳的性能。然而,在相對低點擊率或低最大吞吐量的情況下,低分離級不會帶來任何益處;因此,除非每個Web服務器需要應付成千上百的頁面請求,不然你不會需要用低分離級。通常,需要在多個配置下進行測試,才能決定使用哪種配置。
注意:當在進程外運行ASP應用時(中或低分離級),ASP應用運行在NT 4上的MTS中或Windows 2000的COM+中;就是說,在NT4中,ASP應用運行在Mtx.exe中;在Windows 2000中,ASP應用運行在DllHost.exe中。你可以在任務管理器中看到這些進程在運行。

COM選項
COM組件也有三種配置選項,但不完全對應于ASP的選項。COM組件可以是“無配置的(Unconfigured)”、作為一個庫應用(Library Application)或是作為一個服務應用(Server Application)。“無配置的”意味著組件不注冊到COM+中,組件將在調用者進程空間中運行;即“進程內”。庫應用也是進程內的,但可以從COM+的服務,如安全、事務和上下文支持,中獲益。服務應用則被配置成運行在自己的進程空間內。

“無配置”比庫應用有一點優越性;而庫應用比服務應用在性能上更優越。這是因為庫應用和ASP是在同一個進程內的,而服務應用是運行在自己的進程空間里的。進程間調用比進程內調用的代價高。同樣,在進程間傳遞如記錄集這樣的數據,需要在兩個進程間復制所有的數據。

缺陷!當使用COM服務應用時,要想在ASP和COM間傳送數據,必須保證對象實現了“按值排列(marshall-by-valu)”,或者說MBV。實現了MBV的對象將自身從一個進程復制到另一個進程。這比下面的方法好:對象留在創建者進程,其他進程重復調用創建進程來使用對象。無連接ADO記錄集是MBV,有連接記錄集就不是。
Scripting.Dictonary對象沒有實現MBV,不能在進程之間傳遞。最后,對VB程序員的一個提示:MBV不是通過用ByVal來傳遞參數。MBV是原始組件作者實現的。

怎樣做?
推薦的用可靠性換取性能的配置:

在IIS 4.0上,用ASP的低分離級,并使用MTS服務包。
在IIS 5.0SHANG,用ASP的中分離級,使用COM+的庫應用。
技巧10:使用Option Explicit
在.asp文件中使用Option Explicit。該指示放在.asp文件的頂部,強制開發者在使用任何變量之前必須定義它。許多程序員認為這有助于調試程序,因為它消除了打字錯誤的可能(如將MyXMLString=敲成MyXLMString=)

另外一點可能更加重要:已定義變量比未定義的變量快。ASP每次是用名字來引用未定義變量的;而另一方面,每個已定義變量有一個序號,ASP用這個序號來引用已定義變量。既然Option Explicit強制變量定義,就保證了所有的變量都是已定義的,訪問速度就更快了。

技巧11:在子過程和函數中使用本地變量
本地變量是那些在子過程和函數中定義的變量。在函數和子過程中,訪問本地變量比訪問全局變量更快。使用本地變量也使代碼更干凈,因此盡量使用本地變量吧。

技巧12:將常用數據復制到腳本變量中
當訪問ASP中的COM對象時,應該將常用對象數據復制到腳本變量中。著將減少COM方法調用。而COM方法調用代價相對比訪問腳本數據更高。當訪問Collection和Dictonary對象時,這項技術也能消減高昂的查詢代價。

通常,當準備不止一次訪問一個對象數據時,應該將這個數據放當一個腳本對象中。
這項優化的主要目標是Request變量(Form和QueryString變量)。例如,你的站點傳遞一個叫UserID的QueryString變量,假定在一個特定頁UserID被引用十次。在ASP頁面的頂部,將UserID的值賦給一個變量,來替代十次的調用Request("UserID"),將接生9次COM調用。

在實際中,訪問COM屬性或方法的昂貴代價可能比較隱蔽。下面是一個例子,顯示一段普通的代碼:

Foo.bar.blah.baz = Foo.bar.blah.qaz(1)
If Foo.bar.blah.zaq = Foo.bar.blah.abc Then ' ...

下面是這段代碼運行的步驟:

1. 變量Foo被解析為一個全局對象
2. 變量bar被解析為Foo的一個成員。這觸發一次COM方法調用
3. 變量blash被解析為Foo.bar的一個成員。同樣,這也觸發一次COM方法調用
4. 變量qaz被解析為Foo.bar.blash的一個成員。對,這也觸發一次COM方法調用
5. 調用 Foo.bar.blah.qaz(1)。一個或多個COM方法調用。獲取圖片?
6. 重復步驟1到步驟3來解析baz。系統不知道調用qaz是否會改變對象模型,所以步驟1到步驟3又執行了一次,來解析baz
7. 解析出baz是Foo.bar.blah的一個成員,執行屬性put.
8. 重復步驟1到步驟3來解析zaq
9. 重復步驟1到步驟3來解析abc

正如你所看到的,這是多么低效(并且慢)。快速的方法是按如下代碼寫VBScript:

Set myobj = Foo.bar.blah ' do the resolution of blah ONCE
Myobj.baz = myobj.qaz(1)
If Myobj.zaq = Myobj.abc Then '...

如果你用的是VBScript 5.0或更后的版本,可以使用With語句:

With Foo.bar.blah
  .baz = .qaz(1)
  If .zaq = .abc Then '...
  ...
End With
标签:

相關文章:

推薦設計

最新文章