程式|效能|字串|效能|字串
摘要:大多數 Active Server Pages (ASP) 應用程式都要通過字串串連來建立呈現給使用者的 HTML 格式的資料。本文對幾種建立此 HTML 資料流的方法進行了比較,在特定情況下,某些方法在效能方面要優於其他方法。本文假定您已經具備一定的 ASP 和 Visual Basic 編程方面的知識。
簡介
編寫 ASP 頁面時,開發人員實際上是建立一個格式化的文字資料流,通過 ASP 提供的 Response 對象寫入 網頁用戶端。建立此文字資料流的方法有多種,而您選擇的方法將對 Web 應用程式的效能和可縮放性產生很大影響。很多次,在我協助客戶最佳化其 Web 應用程式的效能時,發現其中一個比較有效方法是更改 HTML 流的建立方式。本文將介紹幾種常用技術,並測試它們對一個簡單的 ASP 頁面的效能所產生的影響。
ASP 設計
許多 ASP 開發人員都遵循良好的軟體工程原則,儘可能地將其代碼模組化。這種設計通常使用一些包含檔案,這些檔案中包含對頁面的特定不連續部分進行格式化產生的函數。這些函數的字串輸出(通常是 HTML 表格代碼)可以通過各種組合建立一個完整的頁面。某些開發人員對此方法進行了改進,將這些 HTML 函數移到 Visual Basic COM 組件中,希望充分利用已編譯的代碼提供的額外效能。
儘管這種設計方法很不錯,但建立組成這些不連續 HTML 程式碼組件的字串所使用的方法將對 Web 網站的效能和可縮放性產生很大的影響,無論實際的操作是在 ASP 包含檔案中執行還是在 Visual Basic COM 組件中執行。
字串串連
請看以下 WriteHTML 函數的代碼片斷。名為 Data 的參數只是一個字串數組,其中包含一些要格式化為表格結構的資料(例如,從資料庫返回的資料)。
Function WriteHTML( Data )Dim nRepFor nRep = 0 to 99 sHTML = sHTML & vbcrlf _ & "<TR><TD>" & (nRep + 1) & "</TD><TD>" _ & Data( 0, nRep ) & "</TD><TD>" _ & Data( 1, nRep ) & "</TD><TD>" _ & Data( 2, nRep ) & "</TD><TD>" _ & Data( 3, nRep ) & "</TD><TD>" _ & Data( 4, nRep ) & "</TD><TD>" _ & Data( 5, nRep ) & "</TD></TR>"NextWriteHTML = sHTMLEnd Function
這是很多 ASP 和 Visual Basic 開發人員建立 HTML 程式碼時常用的方法。sHTML 變數中包含的文本返回到調用代碼,然後使用 Response.Write 寫入用戶端。當然,這還可以表示為直接嵌入不包含 WriteHTML 函數的頁面的類似代碼。此代碼的問題是,ASP 和 Visual Basic 使用的字串資料型別(BSTR 或 Basic 字串)實際上無法更改長度。這意味著每當字串長度更改時,記憶體中字串的原始表示形式都將遭到破壞,而且將建立一個包含新字串資料的新的表示形式:這將增加分配記憶體和解除配置記憶體的操作。當然,ASP 和 Visual Basic 已為您解決了這一問題,因此實際開銷不會立即顯現出來。分配記憶體和解除配置記憶體要求基本運行時代碼解除各個專用鎖定,因此需要大量開銷。當字串變得很大並且有大塊記憶體要被快速連續地分配和解除配置時,此問題變得尤為明顯,就像在大型字串串連期間出現的情況一樣。儘管這一問題對單使用者環境的影響不大,但在伺服器環境(例如,在 Web 服務器上啟動並執行 ASP 應用程式)中,它將導致嚴重的效能和可縮放性問題。
下面,我們回到上述程式碼片段:此代碼中要執行多少個字串分配操作?答案是 16 個。在這種情況下,“&”運算子的每次應用都將導致變數 sHTML 所指的字串被破壞和重新建立。前面已經提到,字串分配的開銷很大,並且隨著字串的增大而增加,因此,我們可以對上述代碼進行改進。
快捷的解決方案
有兩種方法可以緩解字串串連的影響,第一種方法是嘗試減小要處理的字串的大小,第二種方法是嘗試減少執行字串分配操作的數目。請參見下面所示的 WriteHTML 程式碼的修訂版本。
Function WriteHTML( Data )Dim nRepFor nRep = 0 to 99 sHTML = sHTML & ( vbcrlf _ & "<TR><TD>" & (nRep + 1) & "</TD><TD>" _ & Data( 0, nRep ) & "</TD><TD>" _ & Data( 1, nRep ) & "</TD><TD>" _ & Data( 2, nRep ) & "</TD><TD>" _ & Data( 3, nRep ) & "</TD><TD>" _ & Data( 4, nRep ) & "</TD><TD>" _ & Data( 5, nRep ) & "</TD></TR>" )NextWriteHTML = sHTMLEnd Function
乍一看,可能很難發現這段代碼與上一個程式碼範例的差別。其實,此代碼只是在 sHTML = sHTML & 後的內容外面加上了括弧。這實際上是通過更改優先順序,來減小大多數字串串連操作中處理的字串大小。在最初的程式碼範例中,ASP 編譯器將查看等號右邊的運算式,並從左至右進行計算。結果,每次重複都要進行 16 個串連操作,這些操作針對不斷增長的 sHTML 進行。在新版本中,我們提示編譯器更改操作順序。現在,它將按從左至右、從括弧內到括弧外的順序計算運算式。此技術使得每次重複包括 15 個串連操作,這些操作針對的是不會增長的較小字串,只有一個是針對不斷增長的大的 sHTML。圖 1 顯示了這種最佳化方法與標準串連方法在記憶體使用量模式方面的比較。
圖 1:標準串連與加括弧串連在記憶體使用量模式方面的比較
在特定情況下,使用括弧可以對效能和可縮放性產生十分顯著的影響,後文將對此進行進一步的說明。
StringBuilder
我們已經找到瞭解決字串串連問題的快捷方法,在多數情況下,此方法可以達到效能和投入的最佳平衡。但是,如果要進一步提高構建大型字串的效能,需要採用第二種方法,即減少字串分配操作的數目。為此,需要使用 StringBuilder。StringBuilder 是一個類,用於維護可配置的字串緩衝區,管理插入到此緩衝區的新文本片斷,並僅在文本長度超出字串緩衝區長度時對字串進行重新分配。Microsoft .NET 架構免費提供了這樣一個類 (System.Text.StringBuilder),並建議在該環境下進行的所有字串串連操作中使用它。在 ASP 和傳統的 Visual Basic 環境中,我們無法訪問此類,因此需要自行建立。下面是使用 Visual Basic 6.0 建立的 StringBuilder 類樣本(為簡潔起見,省略了錯誤處理代碼)。
Option Explicit' 預設的緩衝區初始大小和增長係數Private Const DEF_INITIALSIZE As Long = 1000Private Const DEF_GROWTH As Long = 1000' 緩衝區大小和增長Private m_nInitialSize As LongPrivate m_nGrowth As Long' 緩衝區和緩衝區計數器Private m_sText As StringPrivate m_nSize As LongPrivate m_nPos As LongPrivate Sub Class_Initialize() ' 設定大小和增長的預設值 m_nInitialSize = DEF_INITIALSIZE m_nGrowth = DEF_GROWTH ' 初始化緩衝區 InitBufferEnd Sub' 設定初始大小和增長數量Public Sub Init(ByVal InitialSize As Long, ByVal Growth As Long) If InitialSize > 0 Then m_nInitialSize = InitialSize If Grow