変数に小さいデータ型を使用してメモリを節約するのは良い習慣ですか?


32

C ++言語を初めて学んだとき、int、floatなどに加えて、これらのデータ型のより小さいバージョンまたは大きいバージョンが言語内に存在することを知りました。たとえば、変数xを呼び出すことができます

int x;
or 
short int x;

主な違いは、short intは2バイトのメモリを使用し、intは4バイトを使用し、short intの値は小さいことですが、これを呼び出してさらに小さくすることもできます。

int x;
short int x;
unsigned short int x;

さらに制限されています。

ここでの私の質問は、プログラム内で変数が取る値に応じて別々のデータ型を使用するのが良い方法かどうかです。これらのデータ型に従って変数を常に宣言することは良い考えですか?


3
フライ級のデザインパターンを知っていますか?「他の同様のオブジェクトと可能な限り多くのデータを共有することにより、メモリ使用を最小限に抑えるオブジェクト。単純な繰り返し表現が許容できない量のメモリを使用する場合、オブジェクトを大量に使用する方法です...」
gnat

5
標準のパッキング/アライメントコンパイラ設定では、変数はいずれにせよ4バイト境界にアライメントされるため、まったく違いはないかもしれません。
ニキエ

36
時期尚早な最適化の古典的なケース。
スカーフリッジ

1
@nikie-x86プロセッサでは4バイト境界に配置される場合がありますが、これは一般的には正しくありません。MSP430は、任意のバイトアドレスにcharを配置し、その他のすべてを偶数バイトアドレスに配置します。AVR-32とARM Cortex-Mは同じだと思います。
uɐɪ

3
質問の2番目の部分は、unsigned何らかの方法で整数を追加するとスペースが少なくなることを意味しています。離散的な表現可能な値の数は同じになります(符号の表現方法に応じて1を指定するか、1を取ります)が、正の値のみにシフトします。
underscore_d

回答:


41

ほとんどの場合、スペースコストはごくわずかであり、心配する必要はありませんが、型を宣言することで、追加情報を心配する必要があります。たとえば、次の場合:

unsigned int salary;

別の開発者に有益な情報を提供しています。給与をマイナスにすることはできません。

short、int、longの違いがアプリケーションでスペースの問題を引き起こすことはほとんどありません。あるデータ型に数値が常に収まるという誤った仮定を誤って行う可能性が高くなります。数値が常に非常に小さいことを100%確信していない限り、常にintを使用する方がおそらく安全です。それでも、目立ったスペースを節約することはできません。


5
確かに、最近では問題が発生することはめったにありませんが、別の開発者が使用するライブラリまたはクラスを設計している場合、それは別の問題です。たぶん、これらのオブジェクトの数百万のストレージが必要になるでしょう。その場合、違いは大きく、この1つのフィールドだけの2MBと比較して4MBです。
-dodgy_coder

30
unsignedこの場合の使用は悪い考えです。給与をマイナスにすることはできませんが、2つの給与のをマイナスにすることもできません。(一般的に、ビットトゥイドリング以外に符号なしを使用し、オーバーフローで動作を定義するのは悪い考えです。)
zvrba

15
@zvrba:2つの給与の違いはそれ自体が給与ではないため、署名された別のタイプを使用することは正当です。
JeremyP

12
@JeremyPはい。ただし、Cを使用している場合(C ++でもこれが当てはまるようです)、符号なし整数の減算は、符号付きintになります。符号付き整数にキャストすると正しい値になる場合がありますが、計算の結果は符号なし整数になります。符号付き/符号なしの計算の奇妙さについては、この回答も参照してください-ビットをいじっているのでなければ、符号なし変数を使用しないでください。
タクロイ

5
@zvrba:差額は金額ですが、給与ではありません。ここで、給与は金額でもあると主張することができます(ほとんどの人が行う入力を検証することで正の数と0に制限されます)が、2つの給与の差自体は給与ではありません。
JeremyP

29

OPは、プログラムを作成しているシステムのタイプについては何も言いませんでしたが、C ++が言及されているため、OPはGBのメモリを搭載した典型的なPCを考えていたと思います。コメントの1つが言うように、その種のメモリでも、配列などの1つのタイプの数百万のアイテムがある場合、変数のサイズは違いを生むことができます。

OPがPCに限定しないため、組み込みシステムの世界に入ると(実際には問題の範囲外ではありません)、データ型のサイズが非常に重要になります。8Kワードのプログラムメモリと368 バイトのRAM のみを備えた8ビットマイクロコントローラーでの簡単なプロジェクトを完了しました。そこで、明らかにすべてのバイトがカウントされます。必要以上に大きな変数を使用することはありません(スペースの観点とコードサイズの両方から、8ビットプロセッサは多くの命令を使用して16ビットと32ビットのデータを操作します)。このような限られたリソースでCPUを使用する理由は何ですか?大量に、彼らはわずか四分の一の費用がかかります。

現在、512Kバイトのフラッシュと128KバイトのRAMを搭載した32ビットMIPSベースのマイクロコントローラーを使用した別の組み込みプロジェクトを行っています(量は約6ドルです)。PCと同様に、「自然な」データサイズは32ビットです。現在では、コードや、charsやshortの代わりにintをほとんどの変数に使用する方が効率的です。ただし、より小さなデータ型が必要かどうかは、あらゆるタイプの配列または構造を考慮する必要があります。大規模システム用のコンパイラとは異なり、構造体の変数が組み込みシステムにパックれる可能性が高くなります。「穴」を避けるために、すべての32ビット変数を最初に、次に16ビット、次に8ビットを常に配置するように注意します。


10
埋め込みシステムに異なるルールが適用されるという事実のために+1。C ++が記載されているという事実は、ターゲットがPCであることを意味するものではありません。私の最近のプロジェクトの1つは、32KのRAMと256KのFlashを備えたプロセッサー上のC ++で書かれました。
uɐɪ

13

答えはシステムによって異なります。一般的に、より小さい型を使用する利点と欠点は次のとおりです。

長所

  • より小さなタイプは、ほとんどのシステムでより少ないメモリを使用します。
  • タイプが小さいと、一部のシステムで計算が高速になります。多くのシステムで、float vs doubleに特に当てはまります。また、より小さいint型は、8ビットまたは16ビットCPUで非常に高速なコードを提供します。

欠点

  • 多くのCPUにはアライメント要件があります。アライメントされていないデータよりもアライメントされたデータに速くアクセスするものもあります。一部のユーザーは、データにアクセスできるようにデータを揃える必要があります。より大きな整数型は、1つの整列された単位に等しいため、ほとんどの場合、整列がずれることはありません。これは、コンパイラが小さな整数を大きな整数に入れることを強制される可能性があることを意味します。そして、小さな型が大きな構造体の一部である場合、アライメントを修正するために、コンパイラーによって構造体の任意の場所にさまざまなパディングバイトが静かに挿入されることがあります。
  • 危険な暗黙の変換。CとC ++には、型キャストなしで暗黙的に変数をより大きなものに昇格させるためのいくつかのあいまいで危険なルールがあります。「整数プロモーションルール」と「通常の算術変換」と呼ばれる2組の暗黙的な変換ルールが互いに絡み合っています。それらの詳細については、こちらをご覧ください。これらのルールは、CおよびC ++のバグの最も一般的な原因の1つです。プログラム全体で同じ整数型を使用するだけで、多くの問題を回避できます。

私のアドバイスはこれを好むことです:

system                             int types

small/low level embedded system    stdint.h with smaller types
32-bit embedded system             stdint.h, stick to int32_t and uint32_t.
32-bit desktop system              Only use (unsigned) int and long long.
64-bit system                      Only use (unsigned) int and long long.

または、stdint.hから、int_leastn_tまたはint_fastn_tnが8、16、32、または64の数字であるint_leastn_ttype を使用できます。アライメントに合わせたより大きなタイプ」。

int_fastn_t は、「これをnバイト長にしたいが、コードの実行速度が速くなる場合、コンパイラは指定されたよりも大きい型を使用する必要がある」ことを意味します。

一般に、さまざまなstdint.hタイプはint、移植性があるため、プレーンなどよりもはるかに優れたプラクティスです。の意図intは、ポータブルにするためだけに指定された幅を与えないことでした。しかし、実際には、特定のシステム上でどれだけ大きくなるかわからないため、移植するのは困難です。


アライメントに関するスポット。私の現在のプロジェクトでは、16ビットMSP430でuint8_tを無償で使用すると、MCUが不思議な方法でクラッシュしました(ほとんどの場合、GCCのせいではなく、どこかで誤ったアクセスが発生しました)。致命的でない場合、8ビットを超えるアーキテクチャで8ビット型を使用することは少なくとも非効率的です。コンパイラは追加の「and reg、0xff」命令を生成します。移植性のために「int / unsigned」を使用し、余分な制約からコンパイラを解放します。
アレクセイ

11

特定のオペレーティングシステムがどのように機能するかに応じて、通常、メモリが最適化されずに割り当てられると予想されるため、バイト、またはワードまたは他の小さなデータタイプを割り当てると、値はレジスタ全体を占有し、そのすべてが非常に大きくなります自分の。ただし、コンパイラまたはインタープリターがこれを解釈する方法は別のものです。たとえば、C#でプログラムをコンパイルする場合、値は物理的にレジスターを占有する可能性がありますが、値が境界チェックされていないことを確認します目的のデータ型の境界を超える値を保存してください。

パフォーマンスの面で、もしあなたがそのような事柄に本当に熱心であれば、ターゲットレジスタのサイズに最も近いデータ型を使用する方が速いかもしれませんが、変数を扱うのをとても簡単にする素敵な構文シュガーのすべてを逃します。

これはどのように役立ちますか?まあ、あなたがコーディングしている状況の種類を決めるのは本当にあなた次第です。私がこれまでに書いたほぼすべてのプログラムで、コンパイラを信頼して物事を最適化し、最も有用なデータ型を使用するだけで十分です。高精度が必要な場合は、より大きな浮動小数点データ型を使用してください。正の値のみで作業する場合は、おそらく符号なし整数を使用できますが、ほとんどの場合、単純にintデータ型を使用するだけで十分です。

ただし、通信プロトコルや何らかの暗号化アルゴリズムの作成など、非常に厳しいデータ要件がある場合、特にデータのオーバーラン/アンダーランに関連する問題を回避しようとする場合は、範囲チェックされたデータ型を使用すると非常に便利です。 、または無効なデータ値。

私が頭の中で特定のデータ型を使用すると考えることができる唯一の他の理由は、コード内で意図を伝えようとしているときです。たとえば、shortintを使用する場合、非常に小さな値の範囲内で正と負の数を許可することを他の開発者に伝えています。


6

以下のようscarfridgeはコメントし、これがあります

時期尚早な最適化の古典的なケース。

メモリ使用量を最適化しようとすると、パフォーマンスの他の領域に影響を与える可能性があり、最適化の黄金律は次のとおりです。

プログラム最適化の最初のルール:しないでください

プログラム最適化の2番目のルール(専門家のみ!):まだやらないでください。」

—マイケルA.ジャクソン

今が最適化の時かどうかを知るためには、ベンチマークとテストが必要です。最適化を対象とするために、コードがどこで非効率であるかを知る必要があります。

最適化されたバージョンのコード、ある時点で単純な実装より実際に優れているかどうかを判断するには、それらを同じデータで並べてベンチマークする必要があります。

また、特定の実装が現行世代のCPUでより効率的であるという理由だけで、常にそうであるとは限らないことを忘れないでください。質問に対する私の答えは、コーディング時にマイクロ最適化は重要ですか?時代遅れの最適化が桁違いの速度低下をもたらした個人的な経験からの例を詳述します。

多くのプロセッサでは、非整列メモリアクセスは、整列メモリアクセスよりも大幅にコストがかかります。構造体にいくつかのショートを詰めることは、どちらかの値に触れるたびにプログラムがパック/アンパック操作を実行する必要があることを意味する場合があります。

このため、最新のコンパイラはあなたの提案を無視します。ニキーのコメントとして:

標準のパッキング/アライメントコンパイラ設定では、変数はいずれにせよ4バイト境界にアライメントされるため、まったく違いはないかもしれません。

次に、コンパイラが危険にさらされていると思います。

テラバイトのデータセットや組み込みのマイクロコントローラーを使用する場合、このような最適化の場所がありますが、私たちのほとんどにとっては、それは実際には問題ではありません。


3

主な違いは、short intは2バイトのメモリを使用し、intは4バイトを使用し、short intの値は小さいことですが、これを呼び出してさらに小さくすることもできます。

これは間違っています。char各タイプのサイズが前のサイズ以上であることに加えて、1バイトおよび1バイトあたり少なくとも8ビットである以外、各タイプが保持するバイト数について仮定することはできません。

パフォーマンス上の利点は、スタック変数にとって非常にわずかです。とにかく、それらは調整/パディングされます。

このため、shortそしてlong実際上持たない、今日使用して、あなたが使用してオフほとんど常により良いですint


もちろん、それをカットしないstdint.h場合に使用するのに完璧な方法もありintます。整数/構造体の巨大な配列を割り当てるintX_t場合は、効率的で型のサイズに依存するため、これは理にかなっています。メガバイトのメモリを節約できるため、これは時期尚早ではありません。


1
実際、64ビット環境の出現により、とはlong異なる場合がありますint。コンパイラがLP64の場合、int32ビットとlong64ビットであり、intsは依然として4バイトにアライメントされていることがわかります(たとえば、私のコンパイラはそうします)。
JeremyP

1
@JeremyPええ、そうでないと言ったのですか?
パブ

短くて長いと主張するあなたの最後の文は、実際には役に立たない。Longには確かに用途があります。ベースタイプとしてのみint64_t
-JeremyP

@JeremyP:intとlong longで問題なく生きることができます。
gnasher729

@ gnasher729:65,000以上の値を保持できるが、10億以下の値を保持できる変数が必要な場合、何を使用しますか? int32_tint_fast32_t、およびlongすべての良いオプションは、long long単に無駄、とあるint非ポータブル。
ベンフォークト

3

これは、一種のOOPおよび/または企業/アプリケーションの観点からのものであり、特定のフィールド/ドメインには適用できないかもしれませんが、私は原始的な強迫観念の概念を持ち出したいです。

アプリケーションのさまざまな種類の情報にさまざまなデータ型を使用することをお勧めします。ただし、深刻なパフォーマンスの問題(測定および検証など)がない限り、このために組み込み型を使用することはおそらくお勧めできません。

アプリケーションでケルビンの温度をモデル化するushort場合uint、「またはケルビンの負の度数の概念は不合理でドメインロジックエラー」であることを示すために、またはを使用します。この背後にあるアイデアは健全ですが、あなたはすべての方法を行っていません。私たちが気づいたのは、負の値を設定できないことです。したがって、ケルビン温度に負の値を割り当てないようにするためにコンパイラーを取得できると便利です。また、温度に対してビット単位の演算を実行できないことも事実です。また、重量(kg)の測定値を温度(K)に追加することはできません。しかし、温度と質量の両方をuints としてモデル化すれば、まさにそれができます。

組み込み型を使用してDOMAINエンティティをモデル化すると、いくつかの厄介なコードと、チェックの欠落や不変式の破損が発生します。型がエンティティの一部をキャプチャしたとしても(負の値は不可)、他の型を逃すことになります(任意の算術式では使用できず、ビットの配列として扱うことはできません)。

解決策は、不変式をカプセル化する新しい型を定義することです。この方法では、お金がお金であり、距離が距離であることを確認でき、それらを加算することはできず、負の距離を作成することはできませんが、負の金額(または負債)を作成することができます。もちろん、これらの型は組み込み型を内部的に使用しますが、これはクライアントから隠されています。パフォーマンス/メモリ消費量についてのご質問に関連して、この種のものは、あなたがその気を見つける必要があり、あなたは物事があなたのドメインエンティティを操作するあなたの機能のインタフェースを変更することなく、内部的に保存されている方法を変更できるようにすることができ、shortあまりにも気ばかりです大。


1

はい、もちろん。uint_least8_t辞書、巨大な定数配列、バッファなどに使用することをお勧めしuint_fast8_tます。処理目的に使用することをお勧めします。

uint8_least_t(ストレージ)-> uint8_fast_t(処理)-> uint8_least_t(ストレージ)。

たとえば、8ビットシンボルからsource、16ビットコードからdictionaries、および32ビットを使用していconstantsます。あなたがそれらで10-15ビットの操作を処理していて、8ビットを出力するよりもdestination

の2ギガバイトを処理する必要があると想像してみましょうsource。ビット操作の量は膨大です。処理中に高速タイプに切り替えると、優れたパフォーマンスボーナスを受け取ります。高速タイプは、CPUファミリごとに異なる場合があります。あなたは含めることができstdint.h、使用してuint_fast8_tuint_fast16_tuint_fast32_t、など

移植性のuint_least8_t代わりに使用できますuint8_t。しかし、現代のCPUがこの機能をどのように使用するのか実際には誰も知りません。VACマシンは博物館の作品です。多分それは行き過ぎです。


1
あなたがリストしたデータ型にポイントがあるかもしれませんが、単にあると述べるのではなく、なぜそれらが優れているのを説明する必要があります。これらのデータ型に不慣れな私のような人々のために、私はあなたが話していることを理解するためにそれらをグーグルで調べなければなりませんでした。
ピーターM
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.