題:
如何壓縮代碼以獲得更多閃存和RAM?
IntelliChick
2010-11-08 09:32:44 UTC
view on stackexchange narkive permalink

我一直在為我們的特定產品開發功能。有人要求將同一功能移植到另一產品。該產品基於M16C微控制器,該微控制器傳統上具有64K閃存和2k RAM。

這是一個成熟的產品,因此,僅還有132字節的Flash和2字節的RAM。

要移植所需的功能(功能本身已進行了優化),我需要1400字節的Flash和〜200字節的RAM。

有人對如何通過代碼壓縮來檢索這些字節有任何建議嗎?當我嘗試壓縮已經存在的工作代碼時,我需要尋找什麼具體的東西?

任何想法都會受到讚賞。

謝謝。

謝謝大家的建議。我將為您提供最新進展並列出有效的步驟和無效的步驟。
好的,這是我嘗試過的工作:提升編譯器版本。優化得到了極大的改善,使我獲得了大約2K的Flash。瀏覽列表文件以檢查特定產品的冗餘和未使用功能(由於通用代碼庫而被繼承),並獲得了更多閃存。
對於RAM,我執行了以下操作:瀏覽映射文件,以檢查使用最多RAM的功能/模塊。我發現遺留代碼的功能非常繁重(12個通道,每個通道都有固定數量的已分配內存),可以通過共享常用通道之間的信息來了解其要實現的目標,並優化RAM的使用。這給了我約200個字節。
如果您有ascii文件,則可以使用8到7位壓縮。為您節省12.5%。使用zip文件進行壓縮和解壓縮將花費更多的代碼,而不僅僅是這樣做。
十七 答案:
Rex Logan
2010-11-08 09:55:17 UTC
view on stackexchange narkive permalink

您有兩種選擇:首先是查找冗餘代碼,然後將其移至單個調用中以消除重複;第二個是刪除功能。

仔細看一下.map文件,看看是否有一些可以刪除或重寫的函數。還要確保確實需要使用正在使用的庫調用。

諸如除法和乘法之類的某些事情可以帶來很多代碼,但是使用移位和更好地使用常量可以使代碼更小。還可以看一下字符串常量和 printf s之類的東西。例如,每個 printf 都會佔用您的rom,但是您可能可以使用幾個共享格式的字符串,而不必一次又一次地重複該字符串常量。

對於內存,請查看是否可以擺脫全局變量,而在函數中使用自動變量。還要避免在主函數中使用盡可能多的變量,因為它們就像全局變量一樣會消耗內存。

感謝您的建議,我肯定可以嘗試大多數建議,但字符串常量除外。它純粹是嵌入式設備,沒有UI,因此代碼內沒有對printf()的調用。希望這些建議能使我滿意,以獲得我需要的1400字節Flash / 200字節RAM。
@IntelliChick,您會驚訝於有多少人在嵌入式設備內部使用printf()進行打印以進行調試或發送至外圍設備。似乎您對此有所了解,但是如果有人在您之前在項目上編寫過代碼,則對其進行檢查不會有什麼壞處。
只是為了擴展我之前的評論,您還會為有多少人添加調試語句而從未刪除它們感到驚訝。即使執行#ifdefs的人有時也會變得懶惰。
太好了,謝謝!我已經繼承了此代碼庫,因此請務必對這些代碼有所了解。我會隨時向大家發布進度,並嘗試跟踪我通過執行操作獲得的內存或閃存字節數,以作為將來可能需要這樣做的其他人員的參考。
這只是一個問題-嵌套函數調用在層與層之間的躍遷如何?這會增加多少開銷?通過具有多個函數調用來保持模塊化或減少函數調用並節省一些字節,是否更好?那有意義嗎?
事後:遞歸函數的開銷將在數十個觸發器的數量級上,這取決於您的特定CPU體系結構如何處理上下文更改,通常這意味著保存所有本地var並將堆棧指針移回來回。
您必須平衡@intellichick,。您的編譯器可以告訴您何時可以更好地內聯函數(更好的編譯器可以做得更好),從而省去了ROM開銷,IAR等編譯器可讓您優化速度或大小或平衡。出色的編譯器中有許多驚人的選項,它們做的很棒,但是請確保使用諸如volatile之類的東西。
user1844
2010-11-08 14:28:23 UTC
view on stackexchange narkive permalink

始終值得一看的是列出文件(彙編器)輸出,以查找您的特定編譯器特別擅長的事情。

例如,您可能會發現局部變量非常昂貴,並且如果應用程序很簡單,值得冒險,將幾個循環計數器移到靜態變量中可以節省大量代碼。

或者數組索引可能非常昂貴,但指針操作便宜得多。反之亦然。

但是第一步是了解彙編語言。

知道編譯器的功能非常重要。您應該看到我的編譯器存在什麼鴻溝。它使嬰兒哭泣(包括我自己)。
Thomas O
2010-11-08 15:22:27 UTC
view on stackexchange narkive permalink

編譯器優化(例如,GCC中的 -Os )可在速度和代碼大小之間實現最佳平衡。避免使用 -O3 ,因為它可能會增加代碼大小。

如果這樣做,您將需要重新測試所有內容!由於編譯器做出了新的假設,優化可能導致工作代碼無法正常工作。
@Robert,僅在使用未定義的語句時為true: a = a ++將在-O0和-O3中進行不同的編譯。
@Thomas不正確。如果您有一個for循環來延遲時鐘週期,則許多優化器將意識到您在其中沒有執行任何操作並將其刪除。這只是一個例子。
@thomas O,您還需要確保小心易失函數定義。優化器將炸毀那些認為自己很了解C但不了解原子運算複雜性的函數。
所有的優點。根據定義,易失性函數/變量不得進行優化。對此進行優化的任何優化器(包括調用時間和內聯)都將被破壞。
我們正在為M16C系列使用IAR C編譯器3.40。我最近從3.21d升級到該版本,以獲取一些內存用於錯誤修復,並且通過升級(IAR進行了一些優化改進),我們確實獲得了一些顯著的Flash空間(〜1K),但是它也破壞了一些現有功能。我們已經解決了問題,但是我懷疑在這種情況下,編譯器優化會帶來更多的好處。還是)感謝你的建議!
mikeselectricstuff
2010-11-08 15:23:08 UTC
view on stackexchange narkive permalink

對於RAM,請檢查所有變量的範圍-您是否在使用可以使用char的整數?緩衝區大於所需的大小嗎?

代碼壓縮在很大程度上取決於應用程序和編碼樣式。您剩下的金額表明,也許代碼已經經過了一些壓縮,但這意味著幾乎沒有剩餘了。

還要仔細看一下整體功能-是否有未真正使用且可以拋棄的東西?

mikeselectricstuff
2010-11-08 21:40:13 UTC
view on stackexchange narkive permalink

如果這是一個舊項目,但此後才開發出編譯器,則可能是更新的編譯器可能會生成較小的代碼

謝謝邁克!我過去曾嘗試過此方法,但已經使用了獲得的空間! :)從IAR C編譯器3.21d移至3.40。
我又升級了一個版本,並設法獲得了更多的Flash以適應該功能。儘管如此,我確實在RAM方面苦苦掙扎,但仍保持不變。 :(
Toby Jaffey
2010-11-08 14:03:42 UTC
view on stackexchange narkive permalink

始終值得檢查編譯器手冊中提供的用於優化空間的選項。

對於gcc -ffunction-sections -fdata-sections -gc-sections 鏈接器標誌非常適合剝離死代碼。

還有其他一些優秀技巧(針對AVR)

這真的有效嗎? [docs](http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Optimize-Options.html#index-ffunction_002dsections-545)說:“指定這些選項時,彙編器和鏈接器將創建更大的對象和可執行文件,而且速度會變慢。”我知道,對於具有Flash和RAM部分的微型計算機,使用單獨的部分是有意義的-文檔中的此聲明不適用於微控制器嗎?
我的經驗是,它對於AVR效果很好
在我使用過的大多數編譯器中,這不能很好地工作。就像使用register關鍵字一樣。您可以告訴編譯器變量已進入寄存器,但是好的優化器會比人工優化(在某些情況下可能會爭論,實際上不可接受)。
當您開始分配位置時,您將迫使編譯器將它們放置在某些位置,這對於高級引導加載程序代碼非常重要,但是在優化器中卻無法處理,因為您為此做出決策時就放棄了對其進行優化的步驟能做。在某些編譯器中,他們將其設計為包含用於代碼用途的部分,這種情況是告訴編譯器更多信息以了解您的用法,這將有所幫助。如果編譯器不建議這樣做,請不要這樣做。
semaj
2010-11-08 22:55:39 UTC
view on stackexchange narkive permalink

您可以檢查分配的堆棧空間和堆空間量。如果這兩個或其中一個或兩個都被過度分配,您也許能夠獲得大量的RAM。

我的猜測是針對一個適合2k RAM的項目,而沒有動態內存分配(使用 malloc calloc 等)。如果是這種情況,您可以假設原始作者為堆分配了一些RAM,從而完全擺脫了堆。

您必須非常小心地減小堆的大小,因為這會導致非常嚴重的錯誤。很難找到。首先將整個堆棧空間初始化為一個已知值(除了0x00或0xff以外的其他值,因為這些值已經很常見了)可能會有所幫助,然後運行系統一段時間以查看有多少未使用的堆棧空間。

這些是非常好的選擇。我會指出,您永遠不要在嵌入式系統中使用malloc。
@Kortuk取決於您對嵌入式的定義以及正在執行的任務
@joby,是的,我知道。在重新啟動為0且沒有linux之類的OS的系統中,Malloc可能非常糟糕。
沒有動態內存分配,沒有使用malloc和calloc的地方。我還檢查了堆分配,並且已經將其設置為0,因此沒有堆分配。當前分配的堆棧大小為254字節,中斷堆棧的大小為128字節。
Craig McQueen
2010-11-09 05:44:53 UTC
view on stackexchange narkive permalink

您的代碼是否使用浮點數學?您也許可以僅使用整數數學來重新實現算法,並消除使用C浮點庫的開銷。例如。在某些應用中,正弦,對數,exp等函數可以用整數多項式逼近代替。

您的代碼是否對任何算法(例如CRC計算)使用大的查詢表?您可以嘗試替換即時計算值的算法的其他版本,而不是使用查找表。需要注意的是,較小的算法很可能較慢,因此請確保您有足夠的CPU週期。

您的代碼是否包含大量常量數據,例如字符串表,HTML頁面或像素圖形(圖標)?如果足夠大(例如10 kB),則可能有必要實施一個非常簡單的壓縮方案來壓縮數據並在需要時即時對其進行解壓縮。

有2個小的查找表,不幸的是,它們都不會有1萬個。而且也沒有使用浮點數學。 :(不過,謝謝您的建議。它們很好。
Prof. Falken
2010-11-08 18:57:10 UTC
view on stackexchange narkive permalink

您可以嘗試將代碼重新排列很多,以使樣式更緊湊。這在很大程度上取決於代碼在做什麼。關鍵是要找到相似的事物,然後彼此重新實現。極端的情況是使用像Forth這樣的高級語言,用這種語言比C或彙編語言更容易實現更高的代碼密度。

這裡是 Forth for M16C

Joel B
2010-11-08 19:41:43 UTC
view on stackexchange narkive permalink

設置編譯器的優化級別。許多IDE都具有允許代碼大小優化的設置,但會浪費編譯時間(在某些情況下甚至可能浪費處理時間)。他們可以通過幾次重新運行優化器,搜索不太常見的可優化模式以及其他一些可能不需要臨時/調試編譯的技巧來完成代碼壓縮。通常,默認情況下,編譯器設置為中等級別的優化。在設置中進行挖掘,您應該能夠找到一些基於整數的優化比例。

目前已優化為最大尺寸。 :) 還是)感謝你的建議。 :)
mikeselectricstuff
2010-11-09 05:27:01 UTC
view on stackexchange narkive permalink

如果您已經在使用IAR之類的專業級編譯器,那麼我認為您將很難通過少量的低級代碼調整來節省大量費用-您需要更多地著眼於刪除功能或以更有效的方式進行零件的重大改寫。您需要成為一個比編寫原始版本的人都要聰明的編碼器。至於RAM,您需要非常仔細地了解當前的使用方式,並查看是否有覆蓋相同RAM用於以下用途的範圍。在不同的時間有不同的事情(為此很方便)。我傾向於在ARM / AVR中使用IAR的默認堆大小和堆棧大小,因此我將首先看這些東西。

謝謝邁克。該代碼已經在大多數地方使用了聯合體,但是我將看看其他地方,這可能仍然會有所幫助。我還將查看所選的堆棧大小,看看是否可以對其進行優化。
我如何知道合適的堆棧大小?
mikeselectricstuff
2010-11-09 21:23:55 UTC
view on stackexchange narkive permalink

還有其他需要檢查的內容-一些架構上的某些編譯器將常量複製到RAM-通常在訪問閃存常量的速度較慢/困難(例如AVR)時使用。 IAR的AVR編譯器需要_ _閃光限定符,以將常量複製到RAM)

謝謝邁克。是的,我已經檢查過-對於M16C IAR C編譯器,它稱為“可寫常量”選項。它將常量從ROM複製到RAM。我的項目未選中此選項。但是真正有效的檢查!謝謝。
supercat
2011-08-29 20:41:52 UTC
view on stackexchange narkive permalink

如果處理器不支持參數/本地堆棧的硬件,但是編譯器仍然嘗試實現運行時參數堆棧,並且如果不需要重新輸入代碼,則可以通過靜態分配自動變量來節省代碼空間。在某些情況下,必須手動完成;在其他情況下,編譯器指令可以做到這一點。有效的手動分配將需要在例程之間共享變量。必須仔細進行這種共享,以確保沒有例程使用另一個例程認為在“範圍內”的變量,但是在某些情況下,代碼大小的好處可能很明顯。

某些處理器已經調用這些約定可以使某些參數傳遞樣式比其他樣式更有效。例如,在PIC18控制器上,如果例程採用單個1字節參數,則可以將其傳遞到寄存器中;否則,可能會將其傳遞給寄存器。如果花費更多,則所有 i>參數必須在RAM中傳遞。如果例程將使用兩個一個字節的參數,則最有效的做法是在全局變量中“傳遞”一個,然後將另一個作為參數傳遞。通過廣泛使用的例程,可以節省更多的費用。如果通過global傳遞的參數是一個單個標誌,或者通常它的值通常為0或255(因為存在將0或255存儲到RAM中的特殊指令),則它們尤其重要。

AlphaGoku
2018-01-23 09:41:45 UTC
view on stackexchange narkive permalink

1。如果您的代碼依賴於許多結構,請確保將結構成員從占用最多內存的結構成員中排序到最少。

例如:“ uint32_t uint16_t uint8_t”而不是“ uint16_t uint8_t uint32_t”

這將確保最小的結構填充。

2。在適用的情況下將const用於變量。這樣可以確保這些變量位於ROM中而不會耗盡RAM

clabacchio
2018-01-23 15:39:20 UTC
view on stackexchange narkive permalink

我在壓縮某些客戶代碼時成功使用了一些技巧(也許很明顯):

  1. 將標誌壓縮到位字段或位掩碼中。這可能是有益的,因為通常布爾值存儲為整數,從而浪費內存。這樣可以節省RAM和ROM,通常不會由編譯器完成。

  2. 在代碼中尋找冗餘,並使用循環或函數執行重複的語句。

  3. 我還通過使用索引數組替換了常量中的許多 if(x == enum_entry)<assignment> 語句來節省了一些ROM,注意將枚舉項用作數組索引

  4. ol>
AngryEE
2010-11-08 19:54:18 UTC
view on stackexchange narkive permalink

如果可以,請使用內聯函數或編譯器宏,而不要使用小型函數。有傳遞參數的大小和速度開銷,可以通過使函數內聯來解決。

對於只有一次調用的函數,任何體面的編譯器都應自動執行此操作。
我發現內聯通常對於速度優化更有用,並且通常以增加大小為代價。
內聯通常會增加代碼大小,除非使用諸如`int get_a(struct x){return x.a;}`這樣的瑣碎函數。
Robert
2010-11-09 19:09:04 UTC
view on stackexchange narkive permalink

將局部變量更改為與CPU寄存器相同的大小。

如果CPU是32位,即使最大值永遠不會超過255,也請使用32位變量。使用8位變量,編譯器將添加代碼以掩蓋高24位。

我首先要看的是for循環變量。

  for(i = 0; i < 100; i ++) 

對於8位變量來說這似乎是一個好地方,但是32位變量可能產生的代碼更少。 / p>

這樣可以節省代碼,但會佔用RAM。
僅當該函數調用位於調用跟踪的最長分支中時,它才會佔用RAM。否則,它將重用某些其他功能已經需要的堆棧空間。
如果它是局部變量,通常為true。如果RAM不足,則全局變量(尤其是數組)的大小是開始尋求節省的好地方。
有趣的是,另一種可能性是用符號替換無符號變量。如果編譯器將無符號short優化為32位寄存器,則它必須添加代碼以確保其值從65535換為零。但是,如果編譯器優化了寄存器的有符號短路,則不需要此類代碼。因為不能保證將短路增加到32767以上會發生什麼,所以不需要編譯器發出代碼來處理。由於這個原因,在我研究過的至少兩個ARM編譯器上,帶符號的短代碼可以小於無符號的短代碼。


該問答將自動從英語翻譯而來。原始內容可在stackexchange上找到,我們感謝它分發的cc by-sa 2.0許可。
Loading...