榨乾壓盡嵌入式系統的效能

2021-06-19 14:39:26 字數 3600 閱讀 8393

嵌入式系統在資源使用上受到極其苛刻的限制:處理器能力杯水車薪,記憶體使用以byte計,同時對系統實時性和時序精確性又有著近乎**的要求。特別是對於持續處理大量資料的實時系統,瞬間的過載導致資料計算出現紕漏,極有可能引起不可恢復的錯誤甚至崩潰。鑑於運算資源的極度稀缺性,對於該類異常的保護通常不可能像pc端程式那樣精巧完備和滴水不漏。現狀是:簡單粗暴,甚至置之不理,往往是嵌入式系統對大部分異常和錯誤的最佳處理策略。

這就對設計和開發人員提出了相當高的要求,在嵌入式領域,不能對每條指令了然於胸的設計人員不能設計出多肥少瘦的系統,而不能對整個系統架構融會貫通的開發人員,也無法將**效能推至極限。

本篇主要從編碼的角度總結出常用的效能優化手段,某些具體實現可能會隨不同的器件和編譯器變化,但原則和思想都是普適的。

演算法與具體應用相關,不作為本篇的重點內容,其優化的目標主要是減少運算量,在有些場景下,目標也可能是減少記憶體訪問量。演算法優化在開發階段前就應該完成了。

值得一提的是,對於工程應用,演算法優化大部分情況下並不需要使優化後演算法與優化前的演算法做到功能完全等效,其損失只要可接受即可。例如,對於通訊中的一些估計演算法,犧牲一點點估計效能,就可能極大地減少運算量。因此,工程上的演算法優化往往意味著在演算法效果和效率之間求平衡。

實現上的優化可以通俗地理解為對**進行功能等效變換,功能不變而效率提公升,這是本篇的重點內容。下面有些效能提公升手段的前提是要對編譯器/器件特性有基本的了解,否則不但無法提公升反而適得其反。

讓工具承擔更多的事情,這應該是大勢所趨,雖然目前程式設計師仍是效能提公升的主要因素,但有時候編譯器還是能做很多任務作的,對於本篇後面總結的各種實現優化,有一些手段本身能直接提公升效能,而另一部分手段則是在為編譯器優化創造條件,如迴圈展開/利用指令並行度。對程式設計師來說,啟用編譯器優化可以做這些事:

1.    提公升編譯優化等級,o0表示編譯器不會對**作任何優化,o1~o3分別代表不同程度的優化,數字越大優化越明顯(原則上)。當然最高等級的優化要謹慎啟用,因為此時編譯器的行為可能遠遠超出你所知道的,這樣可能會出現不該優化的而被優化得情況,導致功能錯誤;

2.    增加各種型別的編譯提示符#paragma。具體使用可參考編譯器文件,其優化功能不外乎這幾類:將函式內聯減少函式呼叫開銷;調整條件分支以利於指令流水;還有指示編譯器不要優化的提示符以防出錯。

3.    對指標增加restrict修飾。如果不加修飾,那麼編譯器對函式內的指標操作都是序列處理的以保證功能正確。如果函式中的指標確實互不相干,那麼用restrict修飾這些指標,就會令編譯器對這些指標的操作並行起來甚至調整先後順序,以此提高執行效率,而且不用擔心有功能問題。

4.    使用其他的一些編譯器選項,如跨函式優化功能。

現代處理器均採用流水線結構,即將一條指令的執行分割成若干階段,這樣可以提高執行的並行度,當前指令在執行的同時對下一條指令進行讀取和解碼。

**中條件語句對應的是條件跳轉指令,但執行完條件跳轉指令後下一條應該執行什麼指令?這在條件跳轉指令執行完成前顯然是無法確定的,但流水設計又要求在當前指令未執行完時就要讀取下一條指令。

假設條件判斷為真時,下一步執行指令a,否則執行指令b,編譯器一般的處理是:在條件跳轉指令執行過程中,同時讀取指令a(也可以是b,這取決於寫**時對分支順序的安排,是程式設計師可控的),如果條件跳轉指令執行結果為真,那麼可以直接執行a,否則處理器將丟棄指令a,再讀取指令b並執行。對於後一種情況,顯然比前一種情況多了一步丟棄和讀取操作,這種情況下流水就被破壞了。在每次迴圈中判斷,這種額外的開銷就更大了。如果條件判斷不依賴於迴圈變數,則可以將判斷放到迴圈外部。

從上可以發現,如果運氣很好,每次判斷都為真,流水是不會被破壞的,從而也沒有額外的開銷,而另一種極端則是每次都破壞了。真實情況是,大部分時候判真判假都有,意味著不能完全避免流水破壞,此時可以將概率較大的分支寫在另乙個分支之前,使流水破壞最小化。

將迴圈展開本身並沒有減少運算量,其目的主要是將**展開後為編譯器提供了更多可選的指令並行組合,這樣編譯器輸出的彙編具有更高的指令並行度,從而提高**效率。迴圈展開的前提是迴圈次數是常量,或者其取值為有限的幾種情況。例如,迴圈次數可能為2,4,5,則可以設三個條件分支,分別對三種情況進行迴圈展開。對於迴圈次數很大的情況如1024,可以考慮2x或4x展開,這樣單次迴圈的操作量放大2倍或4倍,迴圈次數則相應倍數地減少。展開的乙個缺點是重複**看起來比較多,當然也千萬不要為此再去封裝乙個函式,否則就是畫蛇添足了。

對於迴圈次數為常量的情況,好的編譯器也可以自動進行展開而不需要人工干預。展開效果可通過比較彙編**識別,如並行度較高則說明展開效果較好。

對於迴圈體內的計算,某些計算項與迴圈本身無關,即每次迴圈該計算項的結果均一樣,則可以將該項的計算提到迴圈外部,減少無謂的運算。

功能齊全的函式雖然更容易被復用,避免了重複造輪子,但不免對效能帶來影響。這類函式往往會包含多個判斷,且對任意乙個特定場景都存在冗餘的操作。應對方法即針對每個特定場景寫乙個專用函式,場景a下的函式不用考慮場景b,也就不用考慮場景b的操作。缺點是可能會造成**冗餘。

這條方法的思路和 「迴圈無關操作提到迴圈外部」  一致,只是應用的上下文不同,需要縱觀全域性,識別不同模組重複的計算,在入口處計算一次後儲存起來,後續再使用時直接讀起即可,

前面提到判斷會破壞流水從而引入額外的開銷,有些判斷可以通過查表來代替,如下形式:

if ()

else if()

else

可以定義乙個函式指標陣列,元素分別為funca,funcb,funcc,而陣列索引則由判斷條件進行轉化,這對switch語句也同樣適用。如下形式也可作類似優化:

if()

else if()

else

函式內定義乙個區域性陣列並同時初始化,如

unsigned char array[3] =;

編譯器實際上會對此初始化呼叫memcpy。如果僅僅因初始化幾個節而呼叫庫函式,未免過於大動干戈,這樣的開銷是完全不值得的。對這樣的情況可以在**中顯式賦值,而刪除定義時的初始化。如果陣列元素較多,也完全可以自己實現乙個專用函式,因為這樣的庫函式往往屬於前面所說的大而全函式。

對於位域讀取,指令操作為:讀取記憶體,移位,掩碼相與獲取指定位域,而寫則更為複雜,通常對於連續幾個位域的寫操作可以優化實現為:1.掩碼相與,2.移位,3.多項相或。

在具有多層記憶體架構的系統中,記憶體布局是乙個很重要的效能提公升課題。越接近處理核心的記憶體訪問開銷越小,反之則越大,根據這個性質可以得出這樣乙個效能提公升原則:越頻繁訪問的**和資料,越應該放在靠近處理核心的記憶體,而訪問頻度低得**和資料則放在遠離處理核心的記憶體。

cache屬於多級內層中最接近處理核心的記憶體,cache可以顯著提高訪問速度,而提高cache命中率是提公升記憶體訪問效率的關鍵。**和資料區域性性越好,cache命中率越高。一般來說**的cache命中率比較高,一則**本身量不會太大,二則因為**的執行不會太隨機。對於資料則要小心設計,巧妙布局才能充分利用cache,一次處理的資料盡量相鄰存放,盡量不要使用鍊錶和樹等物理上可能不連續的資料結構。

不到萬不得已不用彙編直接編碼,可維護性差,可讀性差,容易出錯。如果經常需要使用彙編,不能說明程式設計師是神一樣的程式設計師,只能說明編譯器是豬一樣的編譯器。

另外,如果乙個系統非要到用彙編才能滿足效能要求,那麼這個系統設計本身是值得再審視的。

不要採取五步一哨,十步一崗式的引數檢查,模組內部的引數檢查可省則省,只對可能導致不可恢復錯誤的引數進行檢查。

1.    前面介紹的各種手段,應優先應用在開銷排在前幾位的函式;

2.    實測確認效果後再確定修改;

3.    先按優化點,再按函式或模組優化。

嵌入式系統的效能優化

嵌入式系統的啟動速度因裝置的效能和 的質量而異,但總體而言,從消費者的角度考慮,系統的啟動速度肯定是越快越好。因此,對嵌入式系統進行效能優化,加快裝置的啟動時間為專案後期必須進行的一項工作。需要注意的是 嵌入式linux裝置的優化不是一蹴而就的,而是乙個不斷優化,不斷改進的過程。現將自己掌握的嵌入式...

嵌入式系統的特點

嵌入式系統一般指非pc系統,它包括硬體和軟體兩部分。硬體包括處理器 微處理器 儲存器及外設器件和i o埠 圖形控制器等。軟體部分包括作業系統軟體 os 要求實時和多工操作 和應用程式程式設計。有時設計人員把這兩種軟體組合在一起。應用程式控制著系統的運作和行為 而作業系統控制著應用程式程式設計與硬體的...

嵌入式系統的組成

整個嵌入式系統而言,可以分為三個部分1.uboot 2.kernel 3.檔案系統。其中kernel中以vfs去支援各種檔案系統,如yaffs,ext3,cramfs等等。yaffs yaffs2是專為嵌入式系統使用nand型快閃儲存器而設計的一種日誌型檔案系統。在核心中以vfs來遮蔽各種檔案系統的...