uint32_t
はuint_fast32_t
よりもはるかに普及しているようです(これは逸話的な証拠だと思います)。しかし、それは私には直観に反するようです。
実装でuint32_t
を使用する場合、ほとんどの場合、本当に必要なのは最大4,294,967,295(通常は65,535〜4,294,967,295のはるかに低い値)までの値を保持できる整数だけです。
'exactly 32 bits'の保証は不要であり、'fastest available> = 32 bits'の保証は必要ないので、uint32_t
を使用するのは奇妙に思えますuint_fast32_t
はまさに正しい考えのようです。さらに、通常は実装されていますが、uint32_t
は実際に存在することが保証されていません。
では、なぜuint32_t
が優先されるのでしょうか?それは単によく知られているのですか、それとも他のものより技術的な利点がありますか?
uint32_t
は、それをサポートするプラットフォームでほぼ同じプロパティを持つことが保証されています。1
uint_fast32_t
は、異なるシステム上での動作を比較してほとんど保証しません。
uint_fast32_t
のサイズが異なるプラットフォームに切り替える場合、uint_fast32_t
を使用するすべてのコードを再テストおよび検証する必要があります。すべての安定性の仮定は窓の外に出ようとしている。システム全体の動作は異なります。
コードを書くとき、サイズが32ビットではないuint_fast32_t
システムに対してaccessを持たないこともあります。
uint32_t
の動作は変わりません(脚注を参照)。
正確性は速度よりも重要です。したがって、時期尚早な正確性は時期尚早な最適化よりも優れた計画です。
uint_fast32_t
が64ビット以上のシステム用のコードを書いていた場合、両方のケースでコードをテストして使用することができます。必要性と機会の両方を除いて、そうすることは悪い計画です。
最後に、uint_fast32_t
を任意の長さまたはインスタンス数で保存している場合、キャッシュサイズの問題とメモリ帯域幅のためにuint32
よりも遅くなる可能性があります。今日のコンピューターは、CPUにバインドされるよりもメモリにバインドされることが多く、uint_fast32_t
は、メモリオーバーヘッドを考慮した後ではなく、単独で高速になります。
1 @chuxがコメントで指摘したように、unsigned
がuint32_t
よりも大きい場合、uint32_t
の算術演算は通常の整数プロモーションを通過し、そうでない場合はuint32_t
のままになります。これはバグを引き起こす可能性があります。完璧なものはありません。
多くの人が
uint32_t
ではなくuint32_fast_t
を使用するのはなぜですか?
注:uint32_fast_t
という名前は、uint_fast32_t
である必要があります。
uint32_t
の仕様はuint_fast32_t
よりも厳密であるため、機能の一貫性が向上します。
uint32_t
長所:
uint32_t
短所:
uint_fast32_t
長所:
uint_fast32_t
短所:
uint32_fast_t
を使用しました。多くの人は、このタイプを必要としないだけで使用しているようです。正しい名前さえ使用しませんでした!uint_fast32_t
は1次近似にすぎません。最終的に、最適なものはコーディングの目標に依存します。非常に幅広いポータビリティまたは特定のニッチなパフォーマンス関数をコーディングしない限り、uint32_t
。を使用します
これらのタイプを使用するとき、別の問題が発生します:int/unsigned
と比較したランク
おそらくuint_fastN_t
は少なくともunsigned
のランクになります。これは指定されていませんが、特定のテスト可能な条件です。
したがって、uintN_t
はuint_fastN_t
よりもunsigned
を狭くする可能性が高くなります。これは、移植性に関してuintN_t
数学を使用するコードは、uint_fastN_t
よりも整数の昇格の対象になる可能性が高いことを意味します。
この懸念により、ポータビリティの利点uint_fastN_t
を選択した数学演算で使用できます。
int32_t
ではなくint_fast32_t
に関する補足:まれなマシンでは、INT_FAST32_MIN
は-2,147,483,647であり、-2,147,483,648ではありません。大きなポイント:(u)intN_t
型は厳密に指定されており、移植可能なコードになります。
多くの人が
uint32_t
ではなくuint32_fast_t
を使用するのはなぜですか?
愚かな答え:
uint32_fast_t
はありません。正しいスペルはuint_fast32_t
です。実用的な答え:
uint32_t
またはint32_t
を実際に使用します。正確に32ビットの符号なしラップアラウンド演算(uint32_t
)または2の補数表現(int32_t
)です。 xxx_fast32_t
型は大きくなる可能性があるため、バイナリファイルに格納したり、パックされた配列や構造体で使用したり、ネットワーク経由で送信したりするのは不適切です。さらに、それらはより速くさえないかもしれません。実用的な答え:
uint_fast32_t
を知らない(または単に気にしない)だけで、おそらく同じセマンティクスを持つプレーンunsigned int
を想定していますが、 16ビットint
sおよびいくつかの珍しい博物館サンプルには、32未満のその他の奇妙なintサイズがあります。UXアンサー:
uint32_t
よりも高速かもしれませんが、uint_fast32_t
は使用に時間がかかります。入力に時間がかかります。特に、Cのドキュメントでスペルとセマンティクスを検索する際に時間がかかります;-)優雅さが重要(明らかに意見に基づく):
uint32_t
は見た目が悪いので、多くのプログラマーが自分のu32
またはuint32
タイプを定義することを好みます...この観点から、uint_fast32_t
は修復できないほど不器用に見えます。友人uint_least32_t
などと一緒にベンチに座っているのも驚きではありません。理由の1つは、unsigned int
が既に「最速」であり、特別なtypedefや何かを含める必要がないことです。したがって、高速で必要な場合は、基本的なint
またはunsigned int
タイプを使用してください。
標準は明示的に最速であることを明示的に保証するものではありませんが、間接的には「実行環境のアーキテクチャによって提案された自然なサイズは自然なサイズです」 3.9.1で。言い換えると、int
(またはその符号なしの対応物)は、プロセッサが最も快適なものです。
もちろん、unsigned int
のサイズはわかりません。知っているのは、それが少なくともshort
と同じくらい大きいことです(short
は少なくとも16ビットでなければならないことを覚えているようですが、今標準!)。通常は単純に4バイトだけですが、理論的にはより大きく、極端な場合はさらに小さくなります(私は個人的には、1980年代の8ビットコンピューター上でさえ、これが当てはまるアーキテクチャに遭遇したことがありません... 私は認知症に苦しんでいることがわかりました、int
は当時明らかに16ビットでした).
C++標準は、<cstdint>
型が何であるか、または何を保証するのかを指定することを気にしません。単に「Cと同じ」というだけです。
uint32_t
、C規格ごとに、保証正確に32ビットを取得します。何も違いはありません。パディングビットも同じです。時にはこれがまさにあなたが必要とするものであり、したがってそれは非常に貴重です。
uint_least32_t
は、サイズが何であれ、32ビットよりも小さくできないことを保証します(ただし、それよりも大きくすることもできます)。時々、しかし正確にウィットまたは「気にしない」よりもはるかにまれに、これはあなたが望むものです。
最後に、uint_fast32_t
は、意図の文書化の目的を除いて、私の意見ではいくぶん不必要です。 C標準では"通常最も高速な整数型を指定"(Wordが "通常"であることに注意)と、あらゆる目的で最も高速である必要はないことを明示的に述べています。言い換えると、uint_fast32_t
はuint_least32_t
とほぼ同じです。これは通常最速であり、保証はありません(ただし、保証はありません)。
ほとんどの場合、正確なサイズを気にしないか、または正確に 32(または64、場合によっては16)ビットにするか、「ドントケア」unsigned int
とにかくtypeは最速です。これはuint_fast32_t
がそれほど頻繁に使用されない理由を説明します。
uint32_t
がitsの範囲に使用されるという証拠を見たことはありません。代わりに、I'veがuint32_t
を使用したほとんどの場合、さまざまなアルゴリズムでデータの正確に4オクテットを保持し、ラップアラウンドとシフトセマンティクスを保証します!
uint32_t
の代わりにuint_fast32_t
を使用する他の理由もあります。多くの場合、安定したABIを提供するためです。さらに、メモリ使用量を正確に知ることができます。これは、uint_fast32_t
からの速度ゲインがどのようなものであれ、wheneverそのタイプがuint32_t
のタイプとは異なる場合、非常に大きなオフセットになります。
65536未満の値の場合、すでに便利なタイプがあり、unsigned int
と呼ばれます(unsigned short
は少なくともその範囲を持つ必要がありますが、unsigned int
はネイティブのWordサイズです)値が4294967296の場合、unsigned long
と呼ばれます。
そして最後に、人々はuint_fast32_t
を使用しません。なぜならそれはタイプするのが面倒に長く、タイプミスしやすいからです:D
いくつかの理由。
要約すると、「高速」タイプは価値のないゴミです。特定のアプリケーションでどのタイプが最速かを本当に把握する必要がある場合は、コンパイラでコードのベンチマークを行う必要があります。
正確性とコーディングの容易さの観点から、uint32_t
は、特にuint_fast32_t
よりも多くの利点があります。これは、上記の多くのユーザーが指摘したように、サイズと算術セマンティクスがより正確に定義されているためです。
おそらく見逃されているのは、uint_fast32_t
の利点の1つ-速いであり、意味のある方法で実現されることはありません。64ビットプロセッサを支配している64ビットプロセッサのほとんどx86-64およびAarch64は32ビットアーキテクチャから発展し、64ビットモードでもfast32ビットネイティブ操作があります。したがって、uint_fast32_t
は、これらのプラットフォームのuint32_t
とまったく同じです。
POWER、MIPS64、SPARCのような「実行された」プラットフォームの一部が64ビットALU操作のみを提供している場合でも、興味深い32ビット操作の 大多数 は問題なく実行できます。 64ビットレジスタの場合:最下位の32ビットで目的の結果が得られます(少なくともすべてのメインストリームプラットフォームでは、少なくとも32ビットをロード/ストアできます)。主な問題は左シフトですが、多くの場合、コンパイラでの値/範囲の追跡の最適化により最適化できます。
時折わずかに遅い左シフトまたは32x32-> 64乗算がdoubleそのような値のメモリ使用量を上回ることを疑います。ほとんどのあいまいなアプリケーションを除きます。
最後に、トレードオフの大部分は「メモリの使用とベクトル化の可能性」(uint32_t
を優先)と命令カウント/速度(uint_fast32_t
を優先)として特徴付けられていますが、それでも私には明らかではありません。はい、一部のプラットフォームではsome32ビット操作の追加の指示が必要になりますが、saveいくつかの指示も必要です。
struct two32{ uint32_t a, b; }
をtwo32{1, 2}
のようなrax
に作成すると、最適化できます 単一のmov rax, 0x20001
に、64ビットバージョンには2つの命令が必要です。原則として、これは隣接する算術演算(同じ演算、異なるオペランド)でも可能であるはずですが、実際にはそれを見ていません。データ型が小さいと、データ構造データを効率的にレジスタにパックするSysV ABIなどの、より優れた最新の呼び出し規則がよく利用されます。たとえば、レジスタrdx:rax
で最大16バイトの構造体を返すことができます。 4つのuint32_t
値(定数から初期化された)を持つ構造体を返す関数の場合、
ret_constant32():
movabs rax, 8589934593
movabs rdx, 17179869187
ret
4つの64ビットuint_fast32_t
を使用した同じ構造では、同じことを行うためにレジスタの移動とメモリへの4つのストアが必要です(そして、呼び出し元は戻り後にメモリから値を読み戻す必要があります):
ret_constant64():
mov rax, rdi
mov QWORD PTR [rdi], 1
mov QWORD PTR [rdi+8], 2
mov QWORD PTR [rdi+16], 3
mov QWORD PTR [rdi+24], 4
ret
同様に、構造体の引数を渡すと、32ビット値がパラメーターで使用可能なレジスターに約2倍密にパックされるため、レジスターの引数が不足してスタックに流出する可能性が低くなります1。
「速度が重要な」場所でuint_fast32_t
を使用することを選択した場合でも、多くの場合、固定サイズタイプが必要な場所があります。たとえば、外部出力の値を、外部入力から、ABIの一部として、特定のレイアウトを必要とする構造の一部として、またはメモリのフットプリントを節約するために値の大規模な集約にuint32_t
を賢く使用するために渡します。 uint_fast32_t
型と `` uint32_t`型のインターフェイスが必要な場所では、(開発の複雑さに加えて)、不要な符号拡張、またはその他のサイズ不一致の関連コードが見つかる場合があります。多くの場合、コンパイラーはこれを最適化することで問題ありませんが、異なるサイズのタイプを混合するときに最適化された出力でこれを見るのはまだ珍しいことではありません。
上記の例のいくつかと、ゴドボルトでより多くの で遊ぶことができます 。
1 明確にするために、構造体をレジスタにしっかりと詰め込むという慣習は、小さな値に対して必ずしも明確な勝利とは限りません。これは、小さい値を使用する前に「抽出」する必要があることを意味します。たとえば、2つの構造体メンバーの合計を返す単純な関数にはmov rax, rdi; shr rax, 32; add edi, eax
が必要ですが、64ビットバージョンの場合、各引数は独自のレジスタを取得し、単一のadd
またはlea
だけが必要です。それでも、「通過中に構造を密にパックする」設計が全体的に理にかなっていることを受け入れた場合、値が小さいほどこの機能を活用できます。
私の理解では、int
は当初、サイズが少なくとも16ビットであることを保証する「ネイティブ」整数型であると想定されていました。当時は「合理的な」サイズと考えられていました。
32ビットプラットフォームがより一般的になったとき、「合理的な」サイズが32ビットに変更されたと言えます。
int
を使用します。int
が少なくとも32ビットであることを保証します。int
で、正確に32ビットであることが保証されています。しかし、64ビットプラットフォームが標準になったとき、次の理由でint
を64ビット整数に拡張した人はいませんでした。
int
のサイズが32ビットであることに依存しています。int
ごとにメモリ使用量を2倍にするのは無理かもしれません。では、なぜuint32_t
よりuint_fast32_t
を好むのでしょうか?同じ理由で、言語、C#およびJavaは常に固定サイズの整数を使用します。プログラマーは、異なるタイプの可能なサイズを考えてコードを作成せず、1つのプラットフォーム用に作成し、そのプラットフォームでコードをテストします。ほとんどのコードは、特定のサイズのデータ型に暗黙的に依存しています。そして、これがuint32_t
がほとんどの場合により良い選択である理由です-振る舞いに関する曖昧さを許しません。
さらに、uint_fast32_t
は、サイズが32ビット以上のプラットフォームで本当に最速の型ですか?あんまり。 Windows上のGCC for x86_64によるこのコードコンパイラを検討してください。
extern uint64_t get(void);
uint64_t sum(uint64_t value)
{
return value + get();
}
生成されたアセンブリは次のようになります。
Push %rbx
sub $0x20,%rsp
mov %rcx,%rbx
callq d <sum+0xd>
add %rbx,%rax
add $0x20,%rsp
pop %rbx
retq
get()
の戻り値をuint_fast32_t
(Windows x86_64では4バイト)に変更すると、次のようになります:
Push %rbx
sub $0x20,%rsp
mov %rcx,%rbx
callq d <sum+0xd>
mov %eax,%eax ; <-- additional instruction
add %rbx,%rax
add $0x20,%rsp
pop %rbx
retq
生成されたコードは、32ビット値を64ビット値に拡張するための関数呼び出し後に追加のmov %eax,%eax
命令を除いてほぼ同じであることに注意してください。
32ビット値のみを使用する場合、このような問題はありませんが、おそらくsize_t
変数(おそらく配列サイズ?)を持つ変数を使用することになり、x86_64では64ビットになります。 Linuxではuint_fast32_t
は8バイトなので、状況は異なります。
多くのプログラマーは、小さな値を返す必要があるときにint
を使用します(たとえば[-32,32]の範囲)。 int
がプラットフォームのネイティブ整数サイズである場合、これは完全に機能しますが、64ビットプラットフォームではないため、プラットフォームネイティブタイプに一致する別のタイプの方が適しています(他の小さい整数で頻繁に使用される場合を除く)サイズ)。
基本的に、標準が何を言っているかにかかわらず、uint_fast32_t
はとにかくいくつかの実装で壊れています。いくつかの場所で生成される追加の命令が必要な場合は、独自の「ネイティブ」整数型を定義する必要があります。または、この目的でsize_t
を使用できます。通常はnative
サイズに一致します(8086のような古いプラットフォームやWindows、Linuxなどを実行できるプラットフォームのみは含まれません)。
int
がネイティブの整数型であると想定されていた別の記号は、「整数プロモーションルール」です。ほとんどのCPUはネイティブでしか操作を実行できないため、32ビットCPUは通常32ビットの加算、減算などしか実行できません(Intel CPUはここでは例外です)。他のサイズの整数型は、ロードおよびストア命令を介してのみサポートされます。たとえば、8ビット値は適切な「8ビット符号付きロード」または「8ビット符号なしロード」命令でロードする必要があり、ロード後に値を32ビットに拡張します。整数の昇格規則がなければ、Cコンパイラは、ネイティブ型よりも小さい型を使用する式にもう少しコードを追加する必要があります。残念ながら、64ビットアーキテクチャでは、コンパイラーは場合によっては追加の命令を発行する必要があります(上記を参照)。
実用的には、uint_fast32_t
はまったく役に立ちません。最も広く普及しているプラットフォーム(x86_64)で誤って定義されており、非常に低品質のコンパイラがない限り、実際にはどこにも利点はありません。概念的には、データ構造/配列で「高速」タイプを使用することは決して意味がありません。より効率的に操作できるタイプから得られる節約は、サイズを大きくするコスト(キャッシュミスなど)によってd小になります。作業データセット。そして、個々のローカル変数(ループカウンター、tempsなど)の場合、通常、非おもちゃのコンパイラは生成されたコードのより大きな型でより効率的であれば動作します。署名されたタイプ、それは決して必要ではありません)。
理論的に有用なバリアントの1つはuint_least32_t
です。これは、32ビット値を格納する必要があるが、正確なサイズの32ビット型を持たないマシンに移植したい場合に使用します。ただし、実際には、心配する必要はありません。
多くの場合、アルゴリズムがデータの配列に対して機能する場合、パフォーマンスを改善する最善の方法は、キャッシュミスの数を最小限にすることです。各要素が小さいほど、より多くの要素をキャッシュに収めることができます。これが、64ビットマシンで32ビットポインターを使用するためにまだ多くのコードが記述されている理由です。4GiBに近いデータは必要ありませんが、すべてのポインターとオフセットを作成するコスト4バイトではなく8バイトが必要になります。
IPv4アドレスなど、正確に32ビットを必要とするように指定されたいくつかのABIとプロトコルもあります。これがuint32_t
の真の意味です。CPUで効率的かどうかに関係なく、exactly 32ビットを使用します。これらはlong
またはunsigned long
として宣言されていたため、64ビット移行中に多くの問題が発生しました。少なくとも2³²-1までの数値を保持する符号なしの型が必要な場合は、最初のC標準が発表されて以来、unsigned long
の定義になっています。ただし、実際には、十分な古いコードは、long
が任意のポインターまたはファイルオフセットまたはタイムスタンプを保持できると想定し、十分に古いコードは、正確に32ビット幅であると想定したため、コンパイラは必ずしもlong
あまり多くのものを壊さずにint_fast32_t
と同じ。
理論的には、プログラムがuint_least32_t
を使用し、計算のためにuint_least32_t
要素をuint_fast32_t
変数にロードすることは、より将来性があります。 uint32_t
型をまったく持たない実装は、標準に正式に準拠して宣言することさえできます! (多くの既存のプログラムをコンパイルすることはできません。)実際には、int
、uint32_t
、およびuint_least32_t
が同じではなく、利点がないアーキテクチャはありません現在 =、uint_fast32_t
のパフォーマンス。では、なぜ物事を複雑にしすぎているのでしょうか?
しかし、既にlong
があったときにすべての32_t
型が存在する必要があった理由を見てください。これらの仮定は以前に私たちの顔で爆発したことがわかります。正確な幅の32ビット計算がネイティブのWordサイズより遅いマシンでコードがいつか実行される可能性があり、ストレージにuint_least32_t
を、計算にuint_fast32_t
を宗教的に使用した方が良いでしょう。または、その橋に着いたときにその橋を渡り、単純なものが必要な場合は、unsigned long
があります。
直接答える:uint32_t
またはuint_fast32_t
よりもuint_least32_t
が使用される本当の理由は、単に入力しやすいことと、短くなっているため、読んでください:いくつかのタイプの構造体を作成し、それらのいくつかがuint_fast32_t
または類似している場合、それらをint
またはbool
またはCの他のタイプとうまく合わせるのが難しい場合があります。非常に短い(ケースインポイント:char
vs. character
)。もちろん、これをハードデータでバックアップすることはできませんが、他の答えも理由を推測することしかできません。
uint32_t
を好む技術的な理由に関しては、絶対にneed正確な32ビット符号なし整数である場合、このタイプはのみ標準化された選択。他のほとんどすべての場合、他のバリアントが技術的に望ましいです。具体的には、速度が心配な場合はuint_fast32_t
、ストレージスペースが心配な場合はuint_least32_t
です。これらのいずれかの場合にuint32_t
を使用すると、型が存在する必要がないため、コンパイルできなくなる危険があります。
実際には、uint32_t
および関連する型は現在のすべてのプラットフォームに存在しますが、非常にまれな(最近の)DSPまたはジョークの実装を除き、正確な型を使用しても実際のリスクはほとんどありません。同様に、固定幅タイプでは速度のペナルティに直面する可能性がありますが、それらは(現代のCPUでは)もう機能しません。
プログラマーの怠zyのために、ほとんどの場合、短いタイプが単純に勝つ理由です。