web-dev-qa-db-ja.com

Cでメモリ使用量を減らすためのいくつかのベストプラクティスは何ですか?

「メモリ効率の高いCプログラミング」のベストプラクティスは何ですか。主に組み込み/モバイルデバイスの場合、メモリ消費量を少なくするためのガイドラインは何ですか?

A)コードメモリb)データメモリには別のガイドラインがあるべきだと思います

39
user27424

Cでは、はるかに単純なレベルで、次のことを考慮してください。

  • #pragma pack(1)を使用して、構造をバイト整列します
  • 構造体にさまざまなタイプのデータを含めることができるユニオンを使用する
  • フラグと小整数を格納するには、intではなくビットフィールドを使用します
  • 文字列の格納、文字列プールの実装、およびポインタの使用に固定長の文字配列を使用することは避けてください。
  • 列挙された文字列リストへの参照を保存する場合。フォント名、文字列ではなくインデックスをリストに保存します
  • 動的メモリ割り当てを使用する場合は、再割り当てを回避するために、事前に必要な要素の数を計算してください。
27
SmacL

組み込みシステムでの作業に役立つと私が見つけたいくつかの提案:

  • ルックアップテーブルまたはその他の定数データが​​実際にconstを使用して宣言されていることを確認します。 constを使用すると、データを読み取り専用(フラッシュやEEPROMなど)のメモリに保存できます。それ以外の場合は、起動時にデータをRAM)にコピーする必要があります。 、これはフラッシュとRAMスペースの両方を占有します。マップファイルを生成するようにリンカーオプションを設定し、このファイルを調べて、データがメモリマップのどこに割り当てられているかを正確に確認します。

  • 使用可能なすべてのメモリ領域を使用していることを確認してください。たとえば、マイクロコントローラには、多くの場合、使用できるオンボードメモリがあります(外部RAMよりもアクセスが高速な場合もあります)。コンパイラとリンカのオプション設定を使用して、コードとデータが割り当てられるメモリ領域を制御できるはずです。

  • コードサイズを小さくするには、コンパイラの最適化設定を確認してください。ほとんどのコンパイラには、速度またはコードサイズを最適化するためのスイッチがあります。これらのオプションを試して、コンパイルされたコードのサイズを縮小できるかどうかを確認することをお勧めします。そして明らかに、可能な限り重複コードを排除します。

  • システムに必要なスタックメモリの量を確認し、それに応じてリンカのメモリ割り当てを調整します( この質問 の回答を参照してください)。スタックの使用量を減らすには、スタックに大きなデータ構造を配置しないようにします(「大きい」の値が関係する場合)。

20
ChrisN

可能な限り、固定小数点/整数演算を使用していることを確認してください。多くの開発者は、単純なスケーリングされた整数演算で十分な場合に、浮動小数点演算を使用します(パフォーマンスの低下、ライブラリとメモリの使用量の増加に加えて)。

16
Dan

すべての良い推奨事項。これが私が有用だと思ったいくつかの設計アプローチです。

  • バイトコーディング

専用のバイトコード命令セット用のインタプリタを作成し、その命令セットにできるだけ多くのプログラムを記述します。特定の操作で高いパフォーマンスが必要な場合は、それらをネイティブコードにして、インタープリターから呼び出します。

  • コード生成

入力データの一部が非常にまれにしか変更されない場合は、アドホックプログラムを作成する外部コードジェネレーターを使用できます。これは、より一般的なプログラムよりも小さくなり、実行速度が速くなり、めったに変更されない入力にストレージを割り当てる必要がなくなります。

  • データ嫌いになる

最小限のデータ構造を保存できるのであれば、多くのサイクルを無駄にすることをいとわないでください。通常、パフォーマンスの低下はほとんどありません。

9
Mike Dunlavey

ほとんどの場合、アルゴリズムを慎重に選択する必要があります。 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)に収まるようにすることです。

8
strager

独自のメモリアロケータを使用して(またはシステムのアロケータを慎重に使用して)、メモリの断片化を回避してください。

1つの方法は、「スラブアロケーター」(たとえば、この 記事 を参照)と、さまざまなサイズのオブジェクト用の複数のメモリプールを使用することです。

8
nimrodm

すべてのメモリを事前に事前に割り当てる(つまり、起動の初期化を除いてmalloc呼び出しを行わない)ことは、決定論的なメモリ使用量に間違いなく役立ちます。それ以外の場合は、さまざまなアーキテクチャが役立つテクニックを提供します。たとえば、特定のARMプロセッサは、通常の32ビットの代わりに16ビット命令を使用することでコードサイズをほぼ半分にする代替命令セット(Thumb)を提供します。もちろん、そうすることで速度が犠牲になります。 ...。

7
Judge Maygarden

一部の解析操作は、バッファにコピーして解析するのではなく、バイトが到着したときにストリームに対して実行できます。

これのいくつかの例:

  1. ステートマシンを使用してNMEASteamを解析し、必要なフィールドのみをより効率的な構造体に収集します。
  2. DOMの代わりにSAXを使用してXMLを解析します。
6
JeffV

1)プロジェクトを開始する前に、使用しているメモリの量を測定する方法で、できればコンポーネントごとにビルドします。そうすれば、変更を加えるたびに、メモリ使用量への影響を確認できます。測定できないものを最適化することはできません。

2)プロジェクトがすでに成熟していてメモリ制限に達した場合(またはメモリの少ないデバイスに移植された場合)、メモリをすでに使用しているものを確認します。

私の経験では、特大のアプリケーションを修正する際のほとんどすべての重要な最適化は、キャッシュサイズの縮小、一部のテクスチャの削除など、少数の変更によるものです(もちろん、これは利害関係者の合意を必要とする機能上の変更です。時間の点で効率的ではありません)、オーディオをリサンプリングし、カスタム割り当てされたヒープの先行サイズを減らし、一時的にのみ使用されるリソースを解放し、必要に応じて再度リロードする方法を見つけます。時折、64バイトで16に減らすことができる構造を見つけることがありますが、それが最も簡単な成果になることはめったにありません。ただし、アプリ内の最大のリストと配列がわかっている場合は、最初に確認する構造体がわかります。

そうそう、メモリリークを見つけて修正します。パフォーマンスを犠牲にすることなく回復できるメモリは、素晴らしいスタートです。

私は過去にコードサイズについて心配してlotの時間を費やしました。主な考慮事項(ただし、ビルド時に測定して、変更を確認できるようにしてください)は次のとおりです。

1)どのコードが何によって参照されているかを調べます。 2要素の構成ファイルを解析するためだけにXMLライブラリ全体がアプリにリンクされていることに気付いた場合は、構成ファイルの形式を変更するか、独自の簡単なパーサーを作成することを検討してください。可能であれば、ソース分析またはバイナリ分析のいずれかを使用して大きな依存関係グラフを描画し、ユーザー数が少ない大きなコンポーネントを探します。わずかなコードの書き直しだけでこれらを切り取ることができる場合があります。外交官を演じる準備をしてください。アプリ内の2つの異なるコンポーネントがXMLを使用していて、それをカットしたい場合は、現在信頼できる既製のライブラリであるものを手作業でロールすることの利点を納得させる必要があるのは2人です。 。

2)コンパイラオプションをいじりまわします。プラットフォーム固有のドキュメントを参照してください。たとえば、インライン化によるデフォルトの許容可能なコードサイズの増加を減らしたい場合があります。少なくとも、GCCでは、通常はコードサイズを増加させない最適化のみを適用するようにコンパイラーに指示できます。

3)アダプター層を作成することを意味する場合でも、可能な場合は、ターゲットプラットフォームに既に存在するライブラリを利用します。上記のXMLの例では、OSがXMLライブラリを使用しているため、ターゲットプラットフォームのメモリに常にXMLライブラリがあり、その場合は動的にリンクしていることがわかります。

4)他の誰かが述べたように、サムモードはARMで役立ちます。パフォーマンスが重要ではないコードにのみ使用し、重要なルーチンをARMに残しておくと、違いに気付くことはありません。

最後に、デバイスを十分に制御できる場合は、巧妙なトリックをプレイできる可能性があります。 UIでは、一度に1つのアプリケーションしか実行できませんか?アプリが必要としないすべてのドライバーとサービスをアンロードします。画面は二重にバッファリングされていますが、アプリは更新サイクルに同期していますか?画面バッファ全体を再利用できる場合があります。

5
Steve Jessop
3
kcwu
  • コードスペースを削減するために、文字列定数の長さを減らし、できるだけ多くの文字列定数を削除します

  • 必要に応じて、アルゴリズムとルックアップテーブルのトレードオフを慎重に検討してください

  • さまざまな種類の変数がどのように割り当てられるかに注意してください。

    • 定数はおそらくコードスペースにあります。
    • 静的変数はおそらく固定メモリ位置にあります。可能であればこれらを避けてください。
    • パラメータはおそらくスタックまたはレジスタに格納されます。
    • ローカル変数は、スタックから割り当てることもできます。最悪の場合にコードのスタックスペースが不足する可能性がある場合は、大きなローカル配列または文字列を宣言しないでください。
    • ヒープがない可能性があります-ヒープを管理するOSがない可能性があります。それは受け入れられますか? malloc()関数が必要ですか?
2
Ben Gartner

このトピックに関する組み込みシステム会議のプレゼンテーションがあります。 2001年からですが、それでも非常に適用可能です。 を参照してください。

また、ターゲットデバイスのアーキテクチャを選択できる場合は、最新のARM、Thumb V2、PowerPC、VLE、MIPS、MIPS16など)を使用するか、既知のコンパクトなターゲットを選択します。 Infineon TriCoreやSHファミリのように、非常に優れたオプションです。NEC V850Eファミリは非常にコンパクトです。または、コードのコンパクト性に優れたAVRに移行してください(ただし、8です)。固定長の32ビットRISC以外は良い選択です!

1
jakobengblom2

他の人が与えた提案に加えて、関数で宣言されたローカル変数は通常スタックに割り当てられることを覚えておいてください。

スタックメモリが制限されている場合、またはスタックのサイズを減らしてヒープまたはグローバルRAMを増やすスペースを確保したい場合は、次のことを考慮してください。

  1. 呼び出しツリーをフラット化して、任意の時点でスタック上の変数の数を減らします。
  2. 大きなローカル変数をグローバルに変換します(使用されるスタックの量は減少しますが、グローバルRAM使用される)の量は増加します)。変数は次のように宣言できます。

    • グローバルスコープ:プログラム全体に表示
    • ファイルスコープで静的:同じファイルで表示
    • 関数スコープで静的:関数内で表示
    • 注:とにかく、これらの変更が行われた場合は、 reentrant コードの問題に注意する必要があります。 preemptive 環境があります。

多くの組み込みシステムには、 スタックオーバーフロー がキャッチされたことを確認するためのスタックモニター診断機能がないため、何らかの分析が必要です。

PS:Stack Overflowを適切に使用するためのボーナス?

1
Tim Henigan

アプリケーションで役立つトリックの1つは、雨の日のメモリの資金を作成することです。クリーンアップタスクに十分な大きさの単一ブロックを起動時に割り当てます。 malloc/newが失敗した場合は、雨の日の資金を解放し、リソースが不足していることをユーザーに通知するメッセージを投稿してください。これは、1990年頃に多くのMacアプリケーションで使用されていた手法でした。

0
plinth

メモリ要件を制約する優れた方法は、動的にリンクできるlibcまたはその他の標準ライブラリに可能な限り依存することです。プロジェクトに含める必要のある追加のDLLまたは共有オブジェクトはすべて、書き込みを回避できる可能性のあるメモリのかなりの部分です。

また、必要に応じて、ユニオンとビットフィールドを使用して、プログラムが処理しているデータの一部のみをメモリにロードし、-Os(gccまたはコンパイラの同等のもの)スイッチを使用してコンパイルしていることを確認します。プログラムサイズを最適化します。

0
Max