什麼是一個高效率的軟體?一個高效率的軟體不僅應該比實現相同功能的軟體運作得更快,還應該消耗更少的系統資源。這篇文章匯集了作者在使用VB進行軟體開發時累積下來的一些經驗,透過一些簡單的例子來向你展示如何寫出高效的VB程式碼。其中包含了一些可能對VB程式設計師非常有幫助的技術。在開始之前,先讓我陳清幾個概念。
讓程式碼一次成型:在我接觸到的程式設計師中,有很多人喜歡先根據功能需求把程式碼寫出來,然後在此基礎上優化程式碼。最後發現為了達到優化的目的,他們只好把程式碼再重新寫一次。所以我建議你在寫程式碼之前就需要考慮最佳化問題。
把握好優化的結果和需要花費的工作之間的關係:通常當完成了一段程式碼,你需要檢查和修改它。在檢查程式碼的過程中,也許你會發現某些循環中的程式碼效率還可以進一步的改進。在這種情況下,許多追求完美的程式設計師也許會立刻修改程式碼。我的建議是,如果修改這段程式碼會使程式的運行時間縮短一秒,你可以修改它。如果只能帶來10毫秒的效能改進,則不做任何改動。這是因為重寫一段程式碼必定會引入新的錯誤,而調試新的程式碼必定會花掉你一定的時間。程式設計師應該在軟體效能和開發軟體所需的工作量之間找一個平衡點,而且10毫秒對使用者來說也是一個不能體會到的差異。
在需要使用物件導向方法的時候盡量使用它;VB提供的機制不完全支援物件導向的設計和編碼,但是VB提供了簡單的類別。大多數人認為使用物件將導致程式碼的效率降低。對於這一點我個人有些不同的意見;考察程式碼的效率不能純粹從運行速度的角度出發,軟體佔用的資源也是需要考慮的因素之一。使用類別可以幫助你在整體上提升軟體的效能,這一點我會在後面的例子中詳細說明。
當你寫VB程式碼的時候,希望你能把上面幾點當作指導你編碼的原則。我把文章分成兩個部分:如何提升程式碼的運行速度和編譯最佳化。
如何提高程式碼的運行速度
下面的這些方法可以幫助你提高程式碼的運行速度:
1.使用整數(Integer)和長整數(Long)
提高程式碼運行速度最簡單的方法莫過於使用正確的資料類型了。也許你不相信,但是正確地選擇資料類型可以大幅提升程式碼的效能。在大多數情況下,程式設計師可以將Single,Double和Currency類型的變數替換為Integer或Long類型的變量,因為VB處理Integer和Long的能力遠高於處理其它幾種資料類型。
在大多數情況下,程式設計師選擇使用Single或Double的原因是因為它們能夠保存小數。但是小數也可以保存在Integer類型的變數中。例如程式中約定有三位小數,那麼只需要將儲存在Integer變數中的數值除以1000就可以得到結果。根據我的經驗,使用Integer和Long取代Single,Double和Currency後,程式碼的運行速度可以提高將近10倍。
2.避免使用變體
對一個VB程式設計師來說,這是再明顯不過的事了。變體類型的變數需要16個位元組的空間來保存數據,而一個整數(Integer)只需要2個位元組。通常使用變體類型的目的是為了減少設計的工作量和程式碼量,也有的程式設計師圖省事而使用它。但是如果一個軟體經過了嚴格設計和按照規範編碼的話,完全可以避免使用變體類型。
這裡順帶提一句,對於Object物件也存在著同樣的問題。請看下面的程式碼:
DimFSO
SetFSO=NewScripting.FileSystemObject
或
DimFSOasobject
SetFSO=NewScripting.FileSystemObject
上面的程式碼由於在申明的時候沒有指定資料類型,在賦值時將浪費記憶體和CPU時間。正確的程式碼應該要像下面這樣:
DimFSOasNewFileSystemObject
3.盡量避免使用屬性
在平常的程式碼中,最常見的比較低效的程式碼就是在可以使用變數的情況下,重複使用屬性(Property),尤其是在循環中。要知道存取變數的速度是存取屬性的速度的20倍左右。下面這段程式碼是很多程式設計師在程式中會使用到的:
DimintConasInteger
ForintCon=0toUbound(SomVar())
Text1.Text=Text1.Text&vbcrlf&SomeVar(intCon)
NextintCon
下面這段程式碼的執行速度是上面程式碼的20倍。
DimintConasInteger
DimsOutputasString
ForintCon=0toUbound(SomeVar())
sOutput=sOutput&vbCrlf&
SomeVar(intCon)
Next
Text1.Text=sOutput
4.盡量使用數組,避免使用集合
除非你必須使用集合(Collection),否則你應該盡量使用陣列。根據測試,數組的存取速度可以達到集合的100倍。這個數字聽起來有點駭人聽聞,但是如果你考慮到集合是一個對象,你就會明白為什麼差異會這麼大。
5.展開小的循環體
在編碼的時候,有可能遇到這種情況:一個循環體只會循環2到3次,而且循環體由幾行程式碼組成。在這種情況下,你可以把循環展開。原因是循環會佔用額外的CPU時間。但是如果循環比較複雜,你就沒有必要這麼做了。
6.避免使用很短的函數
和使用小的循環體相同,呼叫只有幾行程式碼的函數也是不經濟的--呼叫函數所花費的時間或許比執行函數中的程式碼需要更長的時間。在這種情況下,你可以把函數中的程式碼拷貝到原來呼叫函數的地方。
7.減少對子物件的引用
在VB中,透過使用.來實現物件的參考。例如:
Form1.Text1.Text
在上面的例子中,程式引用了兩個物件:Form1和Text1。利用這種方法引用效率很低。但很遺憾的是,沒有辦法可以避免它。程式設計師唯一可以做就是使用With或將用另一個物件保存子物件(Text1)。
註:使用With
WithfrmMain.Text1
.Text="LearnVB"
.Alignment=0
.Tag="Itsmylife"
.BackColor=vbBlack
.ForeColor=vbWhite
EndWith
或者
註:使用另一個物件保存子對象
DimtxtTextBoxasTextBox
SettxtTextBox=frmMain.Text1
TxtTextBox.Text="LearnVB"
TxtTextBox.Alignment=0
TxtTextBox.Tag="Itsmylife"
TxtTextBox.BackColor=vbBlack
TxtTextBox.ForeColor=vbWhite
注意,上述的方法只適用於需要對一個物件的子物件進行操作的時候,下面這段程式碼是不正確的:
WithText1
.Text="LearnVB"
.Alignment=0
.Tag="Itsmylife"
.BackColor=vbBlack
.ForeColor=vbWhite
EndWith
不幸的是,我們常常可以在實際的程式碼中發現類似上面的程式碼。這樣做只會使程式碼的執行速度更慢。原因是With塊編譯後會形成一個分枝,會增加了額外的處理工作。
8.檢查字串是否為空
大多數程式設計師在檢查字串是否為空時會使用下面的方法:
IfText1.Text=""then
註:執行操作
Endif
很不幸,進行字串比較需要的處理量甚至比讀取屬性還要大。因此我建議大家使用下面的方法:
IfLen(Text1.Text)=0then
註:執行操作
Endif
9.移除Next關鍵字後的變數名
在Next關鍵字後面加上變數名稱會導致程式碼的效率下降。我也不知道為什麼會這樣,只是一個經驗而已。不過我想很少有程式設計師會這樣畫蛇添足,畢竟大多數程式設計師都是惜字如金的人。
註:錯誤的程式碼
ForiCount=1to10
註:執行操作
NextiCount
註:正確的程式碼
ForiCount=1to10
註:執行操作
Next
10.使用數組,而不是多個變量
當你有多個保存類似資料的變數時,可以考慮將他們用一個陣列代替。在VB中,數組是最高效的資料結構之一。
11.使用動態數組,而不是靜態數組
使用動態數組對程式碼的執行速度不會產生太大的影響,但是在某些情況下可以節省大量的資源。
12.銷毀對象
無論編寫的是什麼軟體,程式設計師都需要考慮在使用者決定終止軟體運作後釋放軟體所佔用的記憶體空間。但很遺憾的是很多程式設計師對這一點好像不是很在意。正確的做法是在退出程序前需要銷毀程式中使用的物件。例如:
DimFSOasNewFileSystemObject
註:執行操作
註:銷毀對象
SetFSO=Nothing
對於窗體,可以進行卸載:
UnloadfrmMain
或
SetfrmMain=Nothing
13.變長和定長字串
從技術上來說,與變長字串相比,定長字串需要較少的處理時間和空間。但是定長字串的缺點在於在很多情況下,你需要呼叫Trim函數以去除字串末的空字符,這樣反而會降低程式碼效率。所以除非是字串的長度不會變化,否則還是使用變長字串。
14.使用類別模組,而不是ActiveX控件
除非ActiveX控制項涉及使用者介面,否則盡量使用輕量的對象,例如類別。這兩者之間的效率有很大差異。
15.使用內部對象
在涉及到使用ActiveX控制項和DLL的時候,許多程式設計師喜歡將它們編譯好,然後再加入工程中。我建議你最好不要這樣做,因為從VB連接到一個外部物件需要耗費大量的CPU處理能力。每當你呼叫方法或存取屬性的時候,就會浪費大量的系統資源。如果你有ActiveX控製或DLL的原始碼,將它們當作工程的私有物件。
16.減少模組的數量
有些人喜歡將通用的函數保存在模組中,對於這一點我表示贊同。但是在一個模組中只寫上二三十行程式碼就有些可笑了。如果你不是非常需要模組,盡量不要使用它。這樣做的原因是因為只有在模組中的函數或變數被呼叫時,VB才會將模組載入到記憶體中;當VB應用程式退出時,才會從記憶體中卸載這些模組。如果程式碼中只有一個模組,VB只會進行一次載入操作,這樣程式碼的效率就提高了;反之如果程式碼中有多個模組,VB會進行多次載入操作,程式碼的效率會降低。
17.使用物件數組
當設計使用者介面時,對於同樣類型的控件,程式設計師應該盡量使用物件數組。你可以做一個實驗:在視窗上加入100個PictureBox,每個PictureBox都有不同的名稱,執行程式。然後建立一個新的工程,同樣在視窗上新增100個PictureBox,不過這次使用物件數組,執行程序,你可以注意到兩個程式載入時間上的差別。
18.使用Move方法
在改變物件的位置時,有些程式設計師喜歡使用Width,Height,Top和Left屬性。例如:
Image1.Width=100
Image1.Height=100
Image1.Top=0
Image1.Left=0
實際上這樣做效率很低,因為程式修改了四個屬性,而且每次修改之後,視窗都會被重繪。正確的做法是使用Move方法:
Image1.Move0,0,100,100
19.減少圖片的使用
圖片將佔用大量內存,而且處理圖片也需要佔用很多CPU資源。在軟體中,如果可能的話,可以考慮用背景色來取代圖片--當然這只是從技術人員的角度出發看這個問題。
20.使用ActiveXDLL,而不是ActiveX控件
如果你設計的ActiveX物件不涉及使用者介面,使用ActiveXDLL。
編譯最佳化
我所見過的許多VB程式設計師從來沒有使用過編譯選項,也沒有試圖搞清楚各個選項之間的差異。下面讓我們來看看各個選項的具體意義。
1.P-代碼(偽代碼)和本機代碼
你可以選擇將軟體編譯為P-程式碼或本機程式碼。缺省選項是本機代碼。那什麼是P-程式碼和本機碼呢?
P-程式碼:當在VB中執行程式碼時,VB首先是將程式碼編譯為P-程式碼,然後再解釋執行編譯好的P-程式碼。在編譯環境下,使用這種程式碼要比本機程式碼快。選擇P-程式碼後,編譯時VB將偽代碼放入一個EXE檔。
本機代碼:本機代碼是VB6以後才推出的選項。當編譯為EXE檔後,本機程式碼的執行速度比P-程式碼快。選擇本機程式碼後,編譯時VB使用機器指令產生EXE檔。
在使用本機程式碼進行編譯時,我發現有時會引入一些莫名其妙的錯誤。在編譯環境中我的程式碼完全正確地被執行了,但是用本機程式碼選項產生的EXE檔案卻不能正確執行。通常這種情況是在卸載視窗或彈出列印視窗時發生的。我透過在程式碼中加入DoEvent語句解決了這個問題。當然出現這種情況的幾率非常少,也許有些VB程式設計師從來沒有遇過,但它的確存在。
在本機程式碼中還有幾個選項:
a)程式碼速度最佳化:此選項可以編譯出速度較快的執行文件,但執行檔比較大。推薦使用
b)程式碼大小最佳化:此選項可以編譯出比較小的執行文件,但是以犧牲速度為代價的,不建議使用。
c)無最佳化:此選項只是將P-程式碼轉換為本機程式碼,沒有做任何最佳化。在調試代碼時可以使用。
d)針對PentiumPro最佳化:雖然該項不是本機程式碼中的預設選項,但我通常會使用該選項。此選項編譯出的可執行程式在PentiumPro和Pentium2以上的機器上可以運作得更快,而在比較老的機器上要稍稍慢一些。考慮到現在用Pentium2都是落伍,所以推薦大家使用這個選項。
e)產生符號化偵錯資訊:此項在編譯過程中產生一些除錯訊息,使用戶可以利用VisualC++一類的工具來偵錯編譯好的程式碼。使用該選項會產生一個.pdf文件,該文件記錄了可執行文件中的標誌資訊。當程式擁有API函數或DLL呼叫時,該選項還是比較有幫助的。
2.進階優化
進階優化中的設定可以幫助你提高軟體的速度,但是有時候也會引入一些錯誤,因此我建議大家盡量小心地使用它們。如果在程式碼中有比較大的循環體或複雜的數學運算時,選取高階最佳化中的某些項會大幅提升程式碼的效能。如果你使用了進階最佳化功能,我建議你嚴格測試編譯好的檔案。
a)假定無別名:可以提高循環體中程式碼的執行效率,但是在如果透過變數的引用改變變數值的情況下,例如呼叫一個方法,變數的引用作為方法的參數,在方法中改變了變數的值的話,就會引發錯誤。有可能只是傳回的結果錯誤,也有可能是導致程式中斷執行的嚴重錯誤。
b)取消陣列綁定檢查、取消整數溢位檢查和取消浮點錯誤檢查:在程式執行時,如果透過這些檢查發現了錯誤,錯誤處理程式碼會處理這些錯誤。但是如果取消了這些檢查,發生了錯誤程序就無法處理。只有當你確定你的程式碼中不會出現上面的這些錯誤時,你才可以使用這些選項。它們將使軟體的效能得到很大的提升。
c)允許不捨入的浮點操作:選擇此選項可以是編譯出來的程式更快處理浮點操作。它唯一的缺點就是在比較兩個浮點數時可能會導致不正確的結果。
d)取消PentiumFDIV安全檢查:此選項是針對一些舊的Pentium晶片設定的,現在看來已經過時了