web-dev-qa-db-ja.com

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

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;

これはさらに制限的です。

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

32
Bugster

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

unsigned int salary;

あなたは別の開発者に有用な情報を提供しています。給与がマイナスになることはありません。

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

42
Oleksi

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

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

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

29
tcrosley

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

メリット

  • タイプが小さいほど、ほとんどのシステムで使用するメモリが少なくなります。
  • 型が小さいほど、システムによっては計算が速くなります。特に、多くのシステムの浮動小数点と倍精度浮動小数点数に当てはまります。また、int型が小さいほど、8ビットまたは16ビットCPUでコードが大幅に高速になります。

短所

  • 多くのCPUには調整要件があります。アライメントされていないデータよりも速くアライメントされたデータにアクセスするものもあります。一部のmustは、データにアクセスできるようにするためにデータを整列させる必要があります。大きい整数型は、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.

または、int_leastn_tまたはint_fastn_t stdint.hから。nは8、16、32、または64の数値です。int_leastn_tタイプは、「これを少なくともnバイトにしたいが、コンパイラーがアライメントに合わせてより大きなタイプとして割り当ててもかまわない」ことを意味します。

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

一般に、さまざまなstdint.h型は、移植性があるため、プレーンなintなどよりもはるかに優れています。 intの意図は、移植可能にするためだけに特定の幅を指定しないことでした。しかし、実際には、特定のシステムでどれほど大きくなるかわからないため、移植するのは困難です。

13
user29079

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

パフォーマンスに関して、そしてそのようなことについて本当に精通している場合は、ターゲットレジスタサイズに最も近いデータ型を使用するほうが速い可能性がありますが、変数の操作を非常に簡単にする素敵な構文上の糖をすべて見落としています。 。

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

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

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

11
S.Robins

scarfridge がコメントしたように、これは

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

メモリ使用量を最適化しようとするとmightはパフォーマンスの他の領域に影響を与えます 最適化のゴールデンルール は次のとおりです:

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

プログラム最適化の2番目のルール(エキスパートのみ!):まだ実行しないでください。 "

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

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

最適化コードのバージョンisが実際のナイーブ実装よりも実際に優れているかどうかを判断するには、同じように並べてベンチマークする必要がありますデータ。

また、特定の実装が現在の世代のCPUでより効率的であるからといって、それがalwaysになるとは限らないことも覚えておいてください。 私の答え 質問へ コーディング時にマイクロ最適化は重要ですか? は、廃止された最適化によって桁違いのスローダウンが発生した個人的な経験の例を詳しく説明しています。

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

このため、最新のコンパイラーはユーザーの提案を無視します。 nikie コメント:

標準のパッキング/アラインメントコンパイラ設定では、変数はいずれにしても4バイト境界にアラインメントされるため、まったく違いがない場合があります。

次に、危険にさらされているコンパイラを推測します。

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

6
Mark Booth

これは、ある種のOOPおよび/または企業/アプリケーションの観点からのものであり、特定のフィールド/ドメインには適用されない可能性がありますが、私は原始的な執着

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

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

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

解決策は、encapsulatesが不変である新しい型を定義することです。このようにして、お金がお金であり、距離が距離であることを確認できます。それらを加算することはできません。負の距離を作成することはできませんが、負の金額(または借金)を作成することはできます。もちろん、これらの型は組み込み型を内部的に使用しますが、これはクライアントからのhiddenです。パフォーマンス/メモリ消費に関する質問に関して、この種の事柄により、ドメインエンティティを操作する関数のインターフェイスを変更せずに、内部に格納する方法を変更できます。shortも同様です。いまいましい。

3
sara

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

これは誤りです。 charが1バイトでバイトあたり少なくとも8ビットであり、かつ各タイプのサイズが前のサイズ以上である場合を除いて、各タイプが保持するバイト数を推測することはできません。

スタック変数のパフォーマンスのメリットは非常に小さく、とにかく整列/パディングされる可能性があります。

このため、現在shortlongはほとんど使用されておらず、ほとんどの場合intを使用する方がベターです。


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

3
Pubby

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

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

たとえば、sourceから8ビットのシンボル、dictionariesから16ビットのコード、およびいくつかの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
puchu