「メモリ効率の高いCプログラミング」のベストプラクティスは何ですか。主に組み込み/モバイルデバイスの場合、メモリ消費量を少なくするためのガイドラインは何ですか?
A)コードメモリb)データメモリには別のガイドラインがあるべきだと思います
Cでは、はるかに単純なレベルで、次のことを考慮してください。
組み込みシステムでの作業に役立つと私が見つけたいくつかの提案:
ルックアップテーブルまたはその他の定数データが実際にconst
を使用して宣言されていることを確認します。 const
を使用すると、データを読み取り専用(フラッシュやEEPROMなど)のメモリに保存できます。それ以外の場合は、起動時にデータをRAM)にコピーする必要があります。 、これはフラッシュとRAMスペースの両方を占有します。マップファイルを生成するようにリンカーオプションを設定し、このファイルを調べて、データがメモリマップのどこに割り当てられているかを正確に確認します。
使用可能なすべてのメモリ領域を使用していることを確認してください。たとえば、マイクロコントローラには、多くの場合、使用できるオンボードメモリがあります(外部RAMよりもアクセスが高速な場合もあります)。コンパイラとリンカのオプション設定を使用して、コードとデータが割り当てられるメモリ領域を制御できるはずです。
コードサイズを小さくするには、コンパイラの最適化設定を確認してください。ほとんどのコンパイラには、速度またはコードサイズを最適化するためのスイッチがあります。これらのオプションを試して、コンパイルされたコードのサイズを縮小できるかどうかを確認することをお勧めします。そして明らかに、可能な限り重複コードを排除します。
システムに必要なスタックメモリの量を確認し、それに応じてリンカのメモリ割り当てを調整します( この質問 の回答を参照してください)。スタックの使用量を減らすには、スタックに大きなデータ構造を配置しないようにします(「大きい」の値が関係する場合)。
可能な限り、固定小数点/整数演算を使用していることを確認してください。多くの開発者は、単純なスケーリングされた整数演算で十分な場合に、浮動小数点演算を使用します(パフォーマンスの低下、ライブラリとメモリの使用量の増加に加えて)。
すべての良い推奨事項。これが私が有用だと思ったいくつかの設計アプローチです。
専用のバイトコード命令セット用のインタプリタを作成し、その命令セットにできるだけ多くのプログラムを記述します。特定の操作で高いパフォーマンスが必要な場合は、それらをネイティブコードにして、インタープリターから呼び出します。
入力データの一部が非常にまれにしか変更されない場合は、アドホックプログラムを作成する外部コードジェネレーターを使用できます。これは、より一般的なプログラムよりも小さくなり、実行速度が速くなり、めったに変更されない入力にストレージを割り当てる必要がなくなります。
最小限のデータ構造を保存できるのであれば、多くのサイクルを無駄にすることをいとわないでください。通常、パフォーマンスの低下はほとんどありません。
ほとんどの場合、アルゴリズムを慎重に選択する必要があります。 O(1)またはO(log n)のメモリ使用量(つまり、低い)を持つアルゴリズムを目指します。たとえば、連続的なサイズ変更可能な配列(たとえば、std::vector
)ほとんどの場合、リンクリストよりも必要なメモリは少なくなります。
ルックアップテーブルを使用すると、コードサイズおよび速度の両方にメリットがある場合があります。 LUTに64エントリしか必要ない場合、大きなsin/cos/tan関数と比較して、sin/cos/tan(対称性を使用してください!)の場合は16 * 4バイトになります。
圧縮が役立つ場合があります。 RLEのような単純なアルゴリズムは、順次読み取るときに簡単に圧縮/解凍できます。
グラフィックやオーディオを扱っている場合は、さまざまな形式を検討してください。パレットまたはビットパック*のグラフィックスは品質とのトレードオフに適している可能性があり、パレットは多くの画像間で共有できるため、データサイズがさらに削減されます。オーディオは16ビットから8ビット、さらには4ビットに削減でき、ステレオはモノラルに変換できます。サンプリングレートは44.1KHzから22kHzまたは11kHzに下げることができます。これらのオーディオ変換は、データサイズ(そして悲しいことに品質)を大幅に削減し、些細なことです(リサンプリングを除くが、それがオーディオソフトウェアの目的です=])。
*これを圧縮することができると思います。グラフィックスのビットパッキングとは、通常、チャネルあたりのビット数を減らして、各ピクセルが元の3バイトまたは4バイト(それぞれRGB888またはARGB8888)から2バイト(たとえばRGB565またはARGB155)または1バイト(ARGB232またはRGB332)に収まるようにすることです。
独自のメモリアロケータを使用して(またはシステムのアロケータを慎重に使用して)、メモリの断片化を回避してください。
1つの方法は、「スラブアロケーター」(たとえば、この 記事 を参照)と、さまざまなサイズのオブジェクト用の複数のメモリプールを使用することです。
すべてのメモリを事前に事前に割り当てる(つまり、起動の初期化を除いてmalloc呼び出しを行わない)ことは、決定論的なメモリ使用量に間違いなく役立ちます。それ以外の場合は、さまざまなアーキテクチャが役立つテクニックを提供します。たとえば、特定のARMプロセッサは、通常の32ビットの代わりに16ビット命令を使用することでコードサイズをほぼ半分にする代替命令セット(Thumb)を提供します。もちろん、そうすることで速度が犠牲になります。 ...。
一部の解析操作は、バッファにコピーして解析するのではなく、バイトが到着したときにストリームに対して実行できます。
これのいくつかの例:
1)プロジェクトを開始する前に、使用しているメモリの量を測定する方法で、できればコンポーネントごとにビルドします。そうすれば、変更を加えるたびに、メモリ使用量への影響を確認できます。測定できないものを最適化することはできません。
2)プロジェクトがすでに成熟していてメモリ制限に達した場合(またはメモリの少ないデバイスに移植された場合)、メモリをすでに使用しているものを確認します。
私の経験では、特大のアプリケーションを修正する際のほとんどすべての重要な最適化は、キャッシュサイズの縮小、一部のテクスチャの削除など、少数の変更によるものです(もちろん、これは利害関係者の合意を必要とする機能上の変更です。時間の点で効率的ではありません)、オーディオをリサンプリングし、カスタム割り当てされたヒープの先行サイズを減らし、一時的にのみ使用されるリソースを解放し、必要に応じて再度リロードする方法を見つけます。時折、64バイトで16に減らすことができる構造を見つけることがありますが、それが最も簡単な成果になることはめったにありません。ただし、アプリ内の最大のリストと配列がわかっている場合は、最初に確認する構造体がわかります。
そうそう、メモリリークを見つけて修正します。パフォーマンスを犠牲にすることなく回復できるメモリは、素晴らしいスタートです。
私は過去にコードサイズについて心配してlotの時間を費やしました。主な考慮事項(ただし、ビルド時に測定して、変更を確認できるようにしてください)は次のとおりです。
1)どのコードが何によって参照されているかを調べます。 2要素の構成ファイルを解析するためだけにXMLライブラリ全体がアプリにリンクされていることに気付いた場合は、構成ファイルの形式を変更するか、独自の簡単なパーサーを作成することを検討してください。可能であれば、ソース分析またはバイナリ分析のいずれかを使用して大きな依存関係グラフを描画し、ユーザー数が少ない大きなコンポーネントを探します。わずかなコードの書き直しだけでこれらを切り取ることができる場合があります。外交官を演じる準備をしてください。アプリ内の2つの異なるコンポーネントがXMLを使用していて、それをカットしたい場合は、現在信頼できる既製のライブラリであるものを手作業でロールすることの利点を納得させる必要があるのは2人です。 。
2)コンパイラオプションをいじりまわします。プラットフォーム固有のドキュメントを参照してください。たとえば、インライン化によるデフォルトの許容可能なコードサイズの増加を減らしたい場合があります。少なくとも、GCCでは、通常はコードサイズを増加させない最適化のみを適用するようにコンパイラーに指示できます。
3)アダプター層を作成することを意味する場合でも、可能な場合は、ターゲットプラットフォームに既に存在するライブラリを利用します。上記のXMLの例では、OSがXMLライブラリを使用しているため、ターゲットプラットフォームのメモリに常にXMLライブラリがあり、その場合は動的にリンクしていることがわかります。
4)他の誰かが述べたように、サムモードはARMで役立ちます。パフォーマンスが重要ではないコードにのみ使用し、重要なルーチンをARMに残しておくと、違いに気付くことはありません。
最後に、デバイスを十分に制御できる場合は、巧妙なトリックをプレイできる可能性があります。 UIでは、一度に1つのアプリケーションしか実行できませんか?アプリが必要としないすべてのドライバーとサービスをアンロードします。画面は二重にバッファリングされていますが、アプリは更新サイクルに同期していますか?画面バッファ全体を再利用できる場合があります。
この本をお勧めします スモールメモリソフトウェア:メモリが限られているシステムのパターン
コードスペースを削減するために、文字列定数の長さを減らし、できるだけ多くの文字列定数を削除します
必要に応じて、アルゴリズムとルックアップテーブルのトレードオフを慎重に検討してください
さまざまな種類の変数がどのように割り当てられるかに注意してください。
このトピックに関する組み込みシステム会議のプレゼンテーションがあります。 2001年からですが、それでも非常に適用可能です。 紙 を参照してください。
また、ターゲットデバイスのアーキテクチャを選択できる場合は、最新のARM、Thumb V2、PowerPC、VLE、MIPS、MIPS16など)を使用するか、既知のコンパクトなターゲットを選択します。 Infineon TriCoreやSHファミリのように、非常に優れたオプションです。NEC V850Eファミリは非常にコンパクトです。または、コードのコンパクト性に優れたAVRに移行してください(ただし、8です)。固定長の32ビットRISC以外は良い選択です!
他の人が与えた提案に加えて、関数で宣言されたローカル変数は通常スタックに割り当てられることを覚えておいてください。
スタックメモリが制限されている場合、またはスタックのサイズを減らしてヒープまたはグローバルRAMを増やすスペースを確保したい場合は、次のことを考慮してください。
大きなローカル変数をグローバルに変換します(使用されるスタックの量は減少しますが、グローバルRAM使用される)の量は増加します)。変数は次のように宣言できます。
reentrant
コードの問題に注意する必要があります。 preemptive
環境があります。多くの組み込みシステムには、 スタックオーバーフロー がキャッチされたことを確認するためのスタックモニター診断機能がないため、何らかの分析が必要です。
PS:Stack Overflowを適切に使用するためのボーナス?
アプリケーションで役立つトリックの1つは、雨の日のメモリの資金を作成することです。クリーンアップタスクに十分な大きさの単一ブロックを起動時に割り当てます。 malloc/newが失敗した場合は、雨の日の資金を解放し、リソースが不足していることをユーザーに通知するメッセージを投稿してください。これは、1990年頃に多くのMacアプリケーションで使用されていた手法でした。
メモリ要件を制約する優れた方法は、動的にリンクできるlibcまたはその他の標準ライブラリに可能な限り依存することです。プロジェクトに含める必要のある追加のDLLまたは共有オブジェクトはすべて、書き込みを回避できる可能性のあるメモリのかなりの部分です。
また、必要に応じて、ユニオンとビットフィールドを使用して、プログラムが処理しているデータの一部のみをメモリにロードし、-Os(gccまたはコンパイラの同等のもの)スイッチを使用してコンパイルしていることを確認します。プログラムサイズを最適化します。