共用方式為


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

撰寫快速的程式碼需要瞭解應用程式的所有層面,以及它如何與系統互動。 本文建議一些較明顯的程式碼撰寫技術替代方案,以協助您確保程式碼的時間關鍵區段能令人滿意地執行。

總而言之,改善時間關鍵程式碼需要您:

  • 知道程式的哪些部分必須快速。

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

  • 知道新功能的成本。

  • 知道完成工作所需的最少工作。

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

本文內容:

快取遺漏和分頁錯誤

遺漏的快取命中 (在內部和外部快取) 以及分頁錯誤 (前往次要存放裝置取得程式指示和資料) 會減慢程式的效能。

CPU 快取叫用可能會花費程式 10-20 個時鐘週期。 外部快取叫用可能會花費 20-40 個時鐘週期。 分頁錯誤可能會花費一百萬個時鐘週期(假設處理器處理 5 億個指令/秒,而分頁錯誤的時間為 2 毫秒)。 因此,撰寫減少遺漏快取命中和分頁錯誤數目的程式碼,對程式執行而言最有利。

緩慢程式的一個原因是它們的分頁錯誤或快取遺漏經常多過於必要。 若要避免這個問題,請務必使用具有良好參考位置的資料結構,這表示將相關專案保持在一起。 有時候看起來很棒的資料結構因為參考位置惡劣而最後變得嚇人,而有時候相反的情況也成立。 以下提供兩個範例:

  • 動態配置的連結清單可以減少程式效能。 當您搜尋專案,或周遊清單至結尾時,每個略過的連結可能會遺漏快取或造成分頁錯誤。 基於簡單陣列的清單實作可能較快,因為快取較佳且分頁錯誤較少。 即使您允許陣列更難成長的事實,它仍然可能更快。

  • 使用動態配置連結清單的雜湊表可能會降低效能。 藉由擴充,使用動態配置連結清單存放內容的雜湊表效能可能實質較差。 事實上,在最後的分析中,在陣列進行簡單的線性搜尋可能實際上較快 (視情況而定)。 使用陣列型雜湊表(所謂的「封閉式雜湊處理」)是經常忽略的實作,通常具有優越的效能。

排序和搜尋

排序與許多一般作業相比,本質就很耗時。 避免不必要的減緩,最好的方式就是避免在關鍵時刻進行排序。 您可以:

  • 延遲排序,直到非效能關鍵時間為止。

  • 在先前的非效能關鍵時間排序資料。

  • 只針對真正需要排序的資料部分進行排序。

有時候,您可以使用排序過的順序建置清單。 請小心,因為如果您需要以排序順序插入資料,可能需要較複雜的資料結構,而其具有不良的參考位置,導致快取遺漏和分頁錯誤。 在所有情況下都沒有任何方法可運作。 請嘗試數種方法,並測量差異。

下面是一些排序的一般祕訣:

  • 使用庫存排序讓 Bug 減到最少。

  • 您可以事先進行以減少排序複雜度的任何工作都是值得的。 如果一次性傳遞您的資料可簡化比較,並將排序從 O(n log n) 減少到 O(n),您幾乎肯定會領先。

  • 請思考排序演算法的參考位置和您預期它執行的資料。

搜尋的替代選擇比排序少。 如果搜尋時間很重要,二進位搜尋或雜湊表查閱幾乎永遠是最佳選擇,但就像排序一樣,您必須將位置牢記在心。 透過小型陣列的線性搜尋可以比透過資料結構進行二進位搜尋更快,而且有許多指標會導致分頁錯誤或快取遺漏。

MFC 和類別庫

Microsoft Foundation Class (MFC) 可大幅簡化程式碼的撰寫。 在撰寫時間關鍵的程式碼時,您應該注意在部分類別中固有的額外負荷。 請檢查您的時間關鍵程式碼所使用的 MFC 程式碼,查看它是否符合您的效能要求。 下列清單顯示您應該注意的 MFC 類別和函式:

  • CString MFC 會呼叫 C 執行時間程式庫,以動態方式配置 CString 記憶體。 一般而言, CString 與任何其他動態配置的字串一樣有效率。 如同任何動態配置的字串,它也有動態配置和釋放的額外負荷。 堆疊上的簡單 char 陣列便經常可以達到相同的目的,且更快速。 請不要使用 CString 來存放常數字串。 請改用 const char *。 您對 CString 物件執行的任何作業都有一些額外負荷。 使用執行時間程式庫 字串函 式的速度可能更快。

  • CArray 提供 CArray 一般陣列不需要的彈性,但您的程式可能不需要。 如果您知道陣列的特定限制,可以改用全域固定陣列。 如果使用 CArray,請使用 CArray::SetSize 來建立其大小,並指定當需要重新配置時,它成長的項目個數。 否則,加入項目可能會導致您的陣列經常重新配置和複製,這樣沒有效率,且可能導致記憶體片段化。 此外,如果您將專案插入陣列中,請將後續專案移至記憶體中, CArray 而且可能需要成長陣列。 這些動作可能導致快取遺漏和分頁錯誤。 如果您瀏覽過 MFC 使用的程式碼,可能會發現您可以撰寫更專屬於您情節的程式碼,以改善效能。 例如,由於 CArray 是一個範本,您可能會針對特定類型提供 CArray 特製化。

  • CListCList 是一個多倍連結的清單,因此元素插入在清單的前端、尾端和已知位置上 POSITION 很快速。 不過,以值或索引來查閱項目需要進行循序搜尋,如果清單很長便可能速度緩慢。 如果您的程式碼不需要雙重連結的清單,您可能想要使用 CList 重新考慮。 使用單向連結清單可節省更新所有作業之另一個指標的額外負荷,以及該指標的記憶體。 額外的記憶體並不大,但它是快取遺漏或分頁錯誤的另一個機會。

  • IsKindOf 此函式可以產生許多呼叫,而且可能會存取不同資料區域中的記憶體,因而導致參考位置不佳。 這對於偵錯組建很有用(例如,在 ASSERT 呼叫中),但嘗試避免在發行組建中使用。

  • PreTranslateMessagePreTranslateMessage當特定視窗樹狀結構需要不同的鍵盤快速鍵,或您必須將訊息處理插入訊息幫浦時使用。 PreTranslateMessage 會修改 MFC 分派訊息。 如果您覆寫 PreTranslateMessage,請只在需要的層級上進行。 例如,如果您只想要特定檢視的子系訊息,就不需要覆寫 CMainFrame::PreTranslateMessage 。 請改成覆寫檢視類別的 PreTranslateMessage

    請勿使用 PreTranslateMessage 來規避一般分派路徑,以處理傳送至任何視窗的任何訊息。 針對該用途,請使用 視窗程式和 MFC 訊息對應。

  • OnIdle閒置事件可能會發生在您不預期的時候,例如 和 WM_KEYUP 事件之間 WM_KEYDOWN 。 計時器可能是觸發程式碼更有效率的方式。 請勿 OnIdle 強制重複呼叫,方法是產生誤訊,或一律從 的 OnIdle 覆寫傳回 TRUE ,這絕不會允許執行緒進入睡眠狀態。 同樣地,計時器或個別執行緒可能較適合。

共用程式庫

重複使用程式碼是合理的。 不過,如果您要使用其他人的程式碼,您應該確定您確實知道它在效能對於您而言至關重要的情況。 瞭解它的最佳方式是逐步執行原始程式碼,或使用 PView 或 效能監視器 等工具進行測量。

請謹慎使用多個堆積。 以 HeapCreateHeapAlloc 建立的額外堆積讓您能管理然後處置相關的配置集合。 請不要認可過多記憶體。 如果您使用多個堆積,請特別注意一開始認可的記憶體數量。

若不用多個堆積,您可以使用 Helper 函式作為程式碼與預設堆積之間的介面。 Helper 函式有助於自訂配置策略,可以改善應用程式的效能。 例如,如果您經常執行小型配置,可能會想將這些配置集中在預設堆積的一個部分。 您可以配置大區塊的記憶體,然後使用 Helper 函式從該區塊再配置出來。 然後,您就不會有多個未使用的記憶體堆積,因為配置會從預設堆積傳出。

不過在某些情況下,使用預設堆積可能減少參考位置。 請使用處理序檢視器、Spy++ 或效能監視器,來測量在堆積之間移動物件的效果。

測量您的堆積,您便可以解釋堆積上的每次配置。 使用 C 執行時間 偵錯堆積常式 來檢查點並傾印您的堆積。 您可以將輸出讀取到例如 Microsoft Excel 的試算表格式,並使用樞紐分析表來檢視結果。 請注意配置的總數、大小和分佈。 將這些結果與工作集的大小進行比較。 另外也請查看相關大小之物件的群集。

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

執行緒

對於背景工作,有效的事件閒置處理可能比使用執行緒更快。 在單一執行緒程式中比較容易瞭解參考位置。

有一項不錯的準則是,唯有在您封鎖的作業系統通知是背景工作的根源時才使用執行緒。 執行緒是這類情況下最好的解決方案,因為封鎖事件的主要執行緒是不切實際的。

執行緒也會造成通訊問題。 您必須使用訊息清單或是藉由配置並使用共用記憶體,來管理執行緒之間的通訊連結。 管理通訊連結通常需要同步處理,以避免競爭情況和死結問題。 這種複雜性很有可能輕易地便演變成 Bug 和效能問題。

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

小型工作集

較小的工作集表示參考位置較佳、較少分頁錯誤,且會有較多的快取命中。 處理序工作集是作業系統直接提供來測量參考位置的最接近度量。

另請參閱

最佳化程式碼