在嵌入式系統開發中,RAM 資源通常非常有限,因此最佳化程式碼以減少 RAM 使用至關重要。同時,效能也是一個重要的考量因素,特別是在實時系統中。本文將探討一些程式碼最佳化技巧,以降低 RAM 使用並提升效能。從函式引數和變數範圍的管理開始,逐步深入到組合語言分析、函式鏈的扁平化、全域變數的利弊權衡,以及記憶體覆寫技術的應用。此外,我們還會介紹如何使用 I/O 線路、計時器和取樣 Profiler 等工具進行程式碼瓶頸分析,並以 LCD 顯示驅動程式最佳化為例,示範如何減少不必要的函式呼叫和指令數量,進而提升程式碼的執行效率。
最佳化程式碼以減少RAM使用
函式引數
函式的輸入引數通常儲存在暫存器中,而不是堆積疊(RAM)中。當函式只有少量引數時,可以鼓勵編譯器使用暫存器。一個好的規則是,每個函式最多有四個引數。如果您使用32位元處理器,這意味著四個32位元變數,而不是將十二個資料塞入四個結構中。
最小化範圍
您可能已經熟悉名稱空間(變數在程式語言中有效的範圍)。變數可以是全域的,也可以在檔案、函式或函式的特定區域中宣告。然而,還有一種範圍:變數被使用的位置。您可能在函式中定義十個變數,使其名稱空間範圍為整個函式,但如果您只在有限區域使用每個變數,其範圍就是該區域,而不是整個函式。
檢視組合語言
要知道您是否成功地與編譯器「心有靈犀」,請檢視組合語言程式碼。不要害怕!我不是建議您學習組合語言,但至少可以稍微瞭解一下。您可以透過檢視 .lst 檔案或在偵錯程式中步過組合語言程式碼來學習到很多關於編譯器和程式碼的知識。
函式鏈
我們曾經看到,函式呼叫會增加堆積疊大小。假設我們有三個函式:main 呼叫 first,而 first 呼叫 second。在這種情況下,堆積疊看起來像左邊的圖 11-2。如果您可以修改設計以使函式結構更平坦,您的堆積疊可以更小,每個堆積疊框架也會更小。
全域變數的優缺點
全域變數有壞名聲,因為它們可能導致意外的行為和難以維護的程式碼。但是,全域變數也有一些好處。例如,如果您有一個函式鏈,並需要在多個函式中使用某個變數,全域變數可以節省RAM。
記憶體覆寫
如果您的系統有一些大型緩衝區,您可以問自己是否所有緩衝區都需要同時使用。例如,顯示緩衝區可能只在更新顯示時使用,而傳輸緩衝區可能只在傳輸資料時使用。透過覆寫記憶體,您可以有效地增加可用RAM。
需要更多速度
在所有需要花費寶貴開發時間的地方,嘗試從處理器中擠出更多週期可能是最糟糕的選擇。嘗試避免這種情況,可能是透過選擇具有足夠速度的處理器。然而,有時您需要使程式碼執行得更快,以回應實時事件、新增新功能或降低功耗。
分析
要知道哪裡需要花費時間最佳化程式碼,您需要知道週期花在哪裡。許多編譯器和作業系統都有一些內建的分析工具,包括分析器。如果您有這些工具,請學習使用它們。如果沒有,您可能需要建立自己的簡單分析器來提供一些見解。
I/O 線路和示波器
如果您有一些開放的I/O線路連線到測試點,它們可以顯示您程式碼中哪些部分需要最佳化。當您進入一個感興趣的函式時,設定一個輸出線路為高電平,當您離開時,設定為低電平。在示波器上觀察這些線路,以檢視每個函式需要多長時間。
最佳化系統效能的方法
在最佳化系統效能時,瞭解系統中哪些部分耗費最多的時間是非常重要的。這可以透過 профилинг(profiling)來實作, профилинг是一種分析系統效能的方法,透過收集系統執行中的資料來找出瓶頸所在。
使用I/O線進行 профилинг
一種簡單的 профилинг方法是使用I/O線來標記不同階段的開始和結束。透過觀察I/O線的變化,可以瞭解系統中不同部分的耗時情況。這種方法可以快速地找出系統中哪些部分需要最佳化。
使用計時器進行 профилинг
另一種 профилинг方法是使用計時器來測量特定程式碼段的執行時間。這可以透過在程式碼中插入計時器函式來實作,例如TimeNow()函式。透過計算程式碼段的開始和結束時間,可以得出程式碼段的執行時間。
程式碼示例
struct sProfile {
uint32_t count;
tTime sum;
tTime start;
tTime end;
};
void main() {
struct sProfile profile;
profile.sum = 0;
while (1) {
profile.start = TimeNow();
ImportantFunction();
profile.end = TimeNow();
profile.sum += profile.end - profile.start;
LogWithNum(eProfiler, eDebug, "Important Function profile: ", profile.sum);
profile.sum = 0;
}
}
使用回應計時器進行 профилинг
如果需要 профилинг一個非常短的程式碼段,可以使用回應計時器。這種計時器可以在程式碼段執行完成後立即觸發,從而得出程式碼段的執行時間。
程式碼示例
profile.start = TimeNow();
while (1) {
ImportantFunction();
profile.end = TimeNow();
profile.sum = profile.end - profile.start;
LogWithNum(eProfiler, eDebug, "Important Function profile: ", profile.sum);
profile.start = TimeNow();
}
分析結果
透過 профилинг,可以得出系統中不同部分的耗時情況。這些資料可以用來找出系統中的瓶頸所在,並對其進行最佳化。同時,也可以透過分析結果來確定最佳化的效果。
圖表翻譯
以下是使用Mermaid語法繪製的 профилинг結果圖表:
flowchart TD
A[開始] --> B[重要函式]
B --> C[計時器]
C --> D[記錄結果]
D --> E[重復]
這個圖表展示了 профилинг過程中不同階段的流程。透過這個圖表,可以清晰地看到系統中不同部分的耗時情況。
最佳化程式碼以提高效能
在最佳化程式碼以提高效能時,瞭解程式碼的瓶頸是非常重要的。這可以透過 профайлер(profiler)來達成,特別是在中斷驅動系統中。透過使用計時器中斷,可以實作取樣 профайлер(sampling profiler),這有助於我們瞭解程式碼中哪些部分消耗了最多的處理器時間。
取樣Profiler
取樣profiler的工作原理是定期儲存傳回指標到RAM區塊中,這樣就可以知道當時正在執行哪個函式。透過分析這些傳回指標,可以計算出每個函式佔用的處理器時間百分比。這種方法在允許巢狀中斷的情況下效果最佳,且profiler計時器應該是唯一可以中斷其他中斷的計時器。
最佳化處理器週期
一旦知道了哪些部分是瓶頸,下一步就是最佳化這些部分。首先,盡量減少變數的存取次數,特別是減少記憶體存取。然後,嘗試將變數儲存在暫存器中,因為暫存器存取比記憶體存取快很多。
記憶體時序
記憶體時序對於效能也有很大影響。等待狀態(wait states)會使處理器等待一定的週期才能夠存取記憶體。透過使用快速的記憶體或將關鍵程式碼儲存在零等待狀態RAM中,可以提高效能。
變數大小
變數的大小也會影響效能。在8位元處理器上,使用較大的變數會導致更多的處理器週期。在較大的處理器上,使用較小的變數可能會導致額外的指令來延伸符號或清除未符號變數。因此,盡量使用與暫存器大小相匹配的變數型別,並避免之間的轉換。
函式鏈
函式呼叫也會導致效能損失,因為處理器需要切換上下文並重填管道。雖然函式對於程式碼維護和正確性是必要的,但過度使用函式封裝可能會導致效能損失。例如,在LCD顯示驅動程式中,為了避免螢幕撕裂,可能需要最佳化函式呼叫以確保資料在螢幕重新整理前被寫入。
例項:LCD顯示驅動程式
在LCD顯示驅動程式中,為了提高效能,可以考慮將關鍵程式碼最佳化為 inline 函式,或是減少函式呼叫的次數。例如,可以將 LcdWriteBuffer 函式中的迴圈內容最佳化為 inline 程式碼,以減少函式呼叫的開銷。
// 最佳化後的LcdWriteBuffer函式
void LcdWriteBuffer(uint16_t* buffer, uint16_t bufLength){
uint16_t i = 0;
while (bufLength) {
// Inline LcdWriteBus函式
IoClear(LCD_SELECT_N); // 選擇晶片
IoWriteBusByte(LCD_BUS, buffer[i] & 0xFF); // 寫入低位元組
IoWriteBusByte(LCD_BUS, (buffer[i] >> 8) & 0xFF); // 寫入高位元組
IoSet(LCD_SELECT_N); // 取消選擇晶片
i++;
bufLength--;
}
}
透過這些最佳化技巧,可以有效地提高程式碼的效能,特別是在嵌入式系統中。然而,需要注意的是,過度最佳化可能會導致程式碼可讀性和維護性的降低,因此需要在最佳化和可維護性之間找到平衡點。
最佳化程式碼:減少不必要的函式呼叫
在最佳化程式碼的過程中,減少不必要的函式呼叫是一個重要的步驟。這可以提高程式碼的效率和效能。在本例中,我們可以看到 LcdWriteBuffer 函式呼叫 IoWriteBusByte 函式多次,這可能會導致效率低下。
合理的最佳化方法
為了最佳化程式碼,我們可以考慮以下幾種方法:
- 行內函式:如果
IoWriteBusByte函式很簡單,可以考慮將其內聯到LcdWriteBuffer函式中,以減少函式呼叫的 overhead。 - 減少指標存取:在
LcdWriteBuffer函式中,指標buffer被存取多次,可以考慮將其存入一個區域性變數中,以減少指標存取的 overhead。 - 使用宏:如果
IoWriteBusByte函式被呼叫多次,可以考慮使用宏來代替函式呼叫,以減少 overhead。
例子
以下是使用行內函式和減少指標存取的例子:
void LcdWriteBuffer(uint16_t* buffer, uint16_t bufLength){
int i;
IoClear(LCD_SELECT_N); // select the chip
uint16_t* ptr = buffer; // 存入區域性變數中
while (bufLength) {
IoWriteBusByte(LCD_BUS, *ptr & 0xFF); // write lower byte
IoWriteBusByte(LCD_BUS, (*ptr >> 8) & 0xFF); // write upper byte
ptr++; // 增加指標
bufLength--;
}
IoSet(LCD_SELECT_N) // deselect the chip
}
在這個例子中,我們將 buffer 指標存入一個區域性變數 ptr 中,以減少指標存取的 overhead。同時,我們也內聯了 IoWriteBusByte 函式,以減少函式呼叫的 overhead。
最佳化程式碼:減少指令數量
在最佳化程式碼的過程中,我們可以透過簡化指令來提高效率。讓我們一步一步地分析和最佳化給定的程式碼。
原始程式碼
原始程式碼是用於將一個 16 位元的數值寫入 LCD 巴士(LCD_BUS)。這個數值存放在 buffer 中,且已經載入一個暫存器中。程式碼分為兩部分:寫入低位元組(lower byte)和寫入高位元組(upper byte)。
寫入低位元組
IoWriteBusByte(LCD_BUS, *buffer & 0xFF); // 寫入低位元組
這個步驟可以分解為以下幾個動作:
- 複製
buffer指標到一個暫存器中。 - 將
i加到buffer指標上,以得到正確的記憶體位址。 - 讀取記憶體在該位址的內容。
- 對內容進行位元 AND 運算,以取得低位元組(& 0xFF)。
- 將結果放入堆積疊中。
- 呼叫
IoWriteBusByte函式,傳遞資料。
寫入高位元組
IoWriteBusByte(LCD_BUS, (*buffer >> 8) & 0xFF); // 寫入高位元組
這個步驟也可以分解為幾個動作,與寫入低位元組類別似,但多了一步右移運算(» 8),以取得高位元組。
最佳化建議
為了減少指令數量和提高效率,我們可以考慮以下最佳化:
- 減少記憶體存取:如果可能,嘗試減少對記憶體的存取次數。例如,讀取
buffer的內容一次,並將其存放在暫存器中,然後進行必要的位元運算。 - 合併運算:如果 assembly 語言支援,嘗試合併一些運算,例如將位元 AND 和右移運算合併成一個指令。
- 使用更有效的資料型別:如果
buffer是一個 16 位元的數值,考慮使用 16 位元的暫存器或變數來存放它,這樣可以減少位元運算的需要。
最佳化後的程式碼
假設我們使用了一種支援合併運算的 assembly 語言,最佳化後的程式碼可能如下:
; 假設 buffer 已經載入一個暫存器中
mov ax, [buffer] ; 載入 buffer 的內容到 ax 暫存器
and ax, 0xFF ; 取得低位元組
IoWriteBusByte LCD_BUS, ax ; 寫入低位元組
shr ax, 8 ; 右移 8 位取得高位元組
and ax, 0xFF ; 確保只有低 8 位有效
IoWriteBusByte LCD_BUS, ax ; 寫入高位元組
這個最佳化過的版本減少了記憶體存取次數和指令數量,從而提高了效率。
最佳化程式碼的重要性
在軟體開發中,最佳化程式碼是非常重要的。最佳化可以提高程式碼的效率、減少記憶體使用量、改善效能等。然而,最佳化也需要考慮到開發時間、維護成本等因素。
從底層實作到高階應用的全面檢視顯示,程式碼最佳化對於提升系統效能至關重要。分析文中提到的多種最佳化策略,包括減少函式呼叫、最小化變數範圍、選用合適的資料型別以及善用暫存器等,可以發現,這些方法的核心目標都在於降低處理器負擔和記憶體存取次數。然而,技術限制深析顯示,過度最佳化可能導致程式碼可讀性下降,增加維護成本,甚至引入新的錯誤。因此,實務落地分析建議,開發者應在效能提升與程式碼可維護性之間取得平衡。針對不同應用場景,例如圖形處理或即時控制系統,應採用不同的最佳化策略。展望未來,隨著編譯器技術的進步和硬體效能的提升,自動化程式碼最佳化工具將扮演更重要的角色,協助開發者更有效率地提升程式碼效能。玄貓認為,開發者應持續關注程式碼最佳化技巧,並結合實際應用需求,才能在有限的資源下,開發出高效能且易於維護的軟體系統。