改善時間關鍵程式碼的秘訣

更新:2007 年 11 月

如果想要快速地撰寫程式碼,您必須要瞭解應用程式的所有層面以及應用程式與系統互動的方式。本主題提供了幾點建議,可以替代其他更平常的程式碼撰寫技術,進而協助您確保程式碼具時間關鍵 (Time Critical) 的部分能夠有效地執行。

改善時間關鍵程式碼的所需條件摘要如下:

  • 知道必須要快速執行程式的哪一個部分。

  • 知道程式碼的大小和執行速度。

  • 知道新功能的耗用情形。

  • 知道完成此工作所需的最小工作量。

您可以使用效能監視器 (perfmon.exe) 收集程式碼效能的詳細資訊。

  • 快取區命中和分頁錯誤

  • 排序和搜尋

  • MFC 和類別程式庫

  • 共用程式庫

  • 堆積

  • 執行緒

  • 小型工作集

快取區遺漏和分頁錯誤

在內部和外部快取區 (Cache) 的快取區遺漏以及分頁錯誤 (移至用於程式指令和資料的次要儲存體) 會拖慢程式的效能。

CPU 快取區命中會耗用程式 10–20 個時脈週期。外部的快取區命中會耗用 20–40 個時脈週期。分頁錯誤會耗用一百萬個時脈週期 (假設處理器每秒處理 500 百萬個指令,且每個分頁錯誤會耗用 2 毫秒的時間)。因此,在程式執行上最需注意的是撰寫能減少快取區命中失誤和分頁錯誤數的程式碼。

程式執行過慢的一個原因是,程式碼發生分頁錯誤或快取區遺漏超過必要次數。若要避免這個問題,使用具有良好區域性參考的資料結構相當重要,意即相關事物會集合在一起。有時看來極佳的資料結構卻因為不良的區域性參考而產生極糟的結果,反之亦然。以下為兩個範例:

  • 動態配置的連結串列 (Linked List) 會降低程式效能,因為當您搜尋某個項目或周遊至某個串列結尾時,每個略過的連結都可能會快取區遺漏或導致分頁錯誤。以簡單陣列為基礎的串列實作 (Implementation) 實際上可能會因為具較佳快取和較少的分頁錯誤而快很多 ─ 即使在陣列會較難以擴充的情況下,它的速度仍然可以快上許多。

  • 使用動態配置的連結串列的雜湊表會降低效能。更進一步地說,使用動態配置的連結串列來儲存內容的雜湊表,基本上可能會有更糟的執行效能。事實上,在最終的分析中,對陣列作簡單線性搜尋實際上可能會快些 (視環境而定)。陣列架構的雜湊表 (又稱為「封閉式雜湊」) 是一種經常具有絕佳的效能卻常遭忽略的實作方式。

排序和搜尋

與許多一般的作業比較,排序原本就是相當費時。避免不必要延緩的最佳方法就是避免在關鍵時間排序。您可以:

  • 延遲排序至非關鍵時間。

  • 提前在非關鍵時間排序資料。

  • 只排序資料中真正需要排序的部分。

有時,您可依排序順序建置串列。請小心,因為如果您需要依排序順序插入資料,便有可能使用到更複雜、但區域性參考不佳的資料結構,而導致快取區遺漏和分頁錯誤。沒有一種方法可適用於所有的情況。請嘗試數種方法,並衡量其差異。

以下為排序的幾個一般提示:

  • 使用內建排序以減少錯誤。

  • 任何能降低排序複雜度的前置處理都是值得的。如果一次傳遞您的資料能夠簡化比較,並將排序時間從 O(n log n) 降低為 O(n),則幾乎可以確定可較快得到結果。

  • 考量排序演算法的區域性參考以及您預期其執行時所用的資料。

與排序比較起來,搜尋的替代方法較少。如果該搜尋具時間關鍵,則二進位搜尋方式或雜湊表查閱幾乎必然是最好的方式,但因用於排序,所以您必須牢記區域性因素。線性搜尋小型陣列時會比二進位搜尋方式具有大量導致分頁錯誤或快取區遺漏指標的資料結構來得快。

MFC 和類別程式庫

MFC 可大幅簡化程式碼的撰寫。在撰寫時間關鍵程式碼時,您應瞭解部分類別原有的額外負擔。請檢查時間關鍵程式碼所使用的 MFC 程式碼,瞭解其是否符合您的效能需求。下列清單區分您須瞭解的 MFC 類別和函式:

  • CString:MFC 呼叫 C 執行階段程式庫,為 CString 動態配置記憶體。一般說來,CString 的效率與其他任何動態配置的字串一樣。如同任何動態配置的字串,它有動態配置和發行上的耗費。通常,堆疊上的一個簡單的 char 陣列就可擔任相同的目的,且速度更快。請不要使用 CString 來儲存常數字串。而請使用 const char *。以 CString 物件執行的任何作業都有一些耗費。使用執行階段程式庫的字串函式可能會比較快。

  • CArrayCArray 提供標準陣列沒有的彈性,不過您的程式並不一定需要它。如果您知道陣列的特定限制,您便可以改採用全域固定陣列。如果您使用 CArray,請使用 CArray::SetSize 來建立其大小,並在必須重新配置時,指定其擴增的項目數。否則,加入項目會導致陣列經常被重新配置及複製,如此會相當缺乏效率,並可能分段記憶體。同時亦請注意,如果您將某個項目插入陣列中,CArray 會移動記憶體中的後續項目,而可能必須擴大陣列。這些動作可能會導致快取區遺漏和分頁錯誤。當您查看 MFC 使用的程式碼時,您可能會發現到,您可在案例中撰寫更特定的程式碼來提升效能。例如,由於 CArray 是一個樣本,您可提供特定類型的 CArray 規格。

  • CListCList 是一個雙向連結串列 (Doubly-Linked List),因此可較快地在串列中的頭、尾和已知位置 (POSITION) 插入項目。依值或索引查閱項目需要使用循序搜尋,不過如果串列較長,速度就會變慢。如果您的程式碼並不需要雙向連結串列,則應重新考慮使用 CList。使用單向連結串列 (Singly-Linked List) 可節省更新所有作業的額外指標以及該指標之記憶體的耗用。額外的記憶體不大,但卻可能另外造成快取區遺漏或分頁錯誤。

  • IsKindOf:這個函式可產生許多呼叫及在不同資料區域內的大量記憶體存取,而導致不佳的參考位置。它對於偵錯組建 (Build) 非常有用 (例如在 ASSERT 呼叫中),但請盡量避免用於發行組建中。

  • PreTranslateMessage:當特定視窗樹狀結構需要不同的鍵盤快速鍵,或當您必須將訊息處理 (Message Handling) 插入訊息幫浦 (Message Pump) 時,請使用 PreTranslateMessagePreTranslateMessage 會改變 MFC 分派訊息。若要覆寫 PreTranslateMessage,請只在必要的層次進行。例如,如果您只對傳送至特定檢視的子系之訊息有興趣,就沒有必要覆寫 CMainFrame::PreTranslateMessage。請改覆寫檢視類別的 PreTranslateMessage

    請不要使用 PreTranslateMessage 來處理任何傳送到視窗的訊息,而繞行正常分派路徑。請使用視窗程序和 MFC 訊息對應 (Message Map) 來達成該目的。

  • OnIdle:閒置 (Idle) 事件可能會發生在未預期的時間,例如在 WM_KEYDOWNWM_KEYUP 事件之間。計時器可能是一種更有效率的觸發 (Trigger) 程式碼方式。請不要利用產生 False 訊息或一直從 OnIdle 的覆寫傳回 TRUE 等方式來強制重複呼叫 OnIdle,這麼做會使您的執行緒永遠無法進入休眠狀態。再次提醒,計時器或個別的執行緒可能較為適合。

共用程式庫

建議您重複使用程式碼。不過,如果您打算使用他人的程式碼,您應該要確定該程式碼在對您來說是關鍵效能之處的實際產生結果。瞭解結果的最佳方法是逐步執行原始程式碼,或是使用例如 PView 或效能監視器等工具加以測量。

堆積

請斟酌使用多重堆積 (Heap)。使用 HeapCreateHeapAlloc 所建立的堆積可讓您管理並接著部署相關的配置集。不要配置太多記憶體。如果您要使用多個堆積,請特別注意初始配置的記憶體數量。

除了使用多重堆積之外,您可使用 Helper 函式做為程式碼與預設堆積之間的介面。Helper 函式有助於自訂可提升應用程式效能的配置策略。例如,如果您經常執行小型配置,您可能會希望將這些配置區域化為預設堆積的其中一部分。您可配置大型記憶體區塊 (Memory Block),然後使用 Helper 函式從該區塊配置。如果這麼做,您將不會有含未用記憶體的額外堆積,因為該配置是取自預設堆積。

然而在某些情況下,使用預設堆積會減少參考的位置。使用處理序檢視器、Spy++ 或效能監視器來測量在堆積之間移動物件所造成的影響。

評估您的堆積,讓您可掌握堆積上的每個配置。使用檢查點的 C 執行階段偵錯堆積常式並傾印堆積。您可將輸出讀取至試算表程式 (如 Microsoft Excel),並使用樞紐分析表來檢視結果。請記下配置的總數、大小和分配。比較這些配置與工作集大小。同時查看相對大小物件的叢集。

您也可以使用效能計數器來監視記憶體用量。

執行緒

針對背景作業,有效的閒置事件處理可能比使用執行緒 (Thread) 來得快。也比較容易瞭解在單一執行緒程式中的參考位置。

良好的判斷規則是,只有當您封鎖的作業系統通知是在背景工作的根位置時,才使用執行緒。在這種情況下,執行緒是最佳的解決方案,因為封鎖某事件中的主執行緒是不實際的。

執行緒也會帶來通訊問題。您必須使用訊息清單、或是配置及使用共用記憶體,來管理執行緒之間的通訊連結。管理通訊連結通常需要同步處理,以避免競爭情形和死結 (Deadlock) 問題。這種複雜性很容易轉變成錯誤和效能問題。

如需詳細資訊,請參閱閒置迴圈處理多執行緒

小型工作集

較小工作集的重要性在於其較佳的區域性參考、較少分頁錯誤,以及較高的快取區命中。處理工作集是指作業系統直接提供用以評估區域性參考的最接近度量 (Metric) 資訊。

請參閱

參考

最佳化程式碼