web-dev-qa-db-ja.com

なぜuint_fast32_tよりもuint32_tが好まれるのですか?

uint32_tuint_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が優先されるのでしょうか?それは単によく知られているのですか、それとも他のものより技術的な利点がありますか?

79
Joost

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がコメントで指摘したように、unsigneduint32_tよりも大きい場合、uint32_tの算術演算は通常の整数プロモーションを通過し、そうでない場合はuint32_tのままになります。これはバグを引き起こす可能性があります。完璧なものはありません。

多くの人がuint32_tではなくuint32_fast_tを使用するのはなぜですか?

注:uint32_fast_tという名前は、uint_fast32_tである必要があります。

uint32_tの仕様はuint_fast32_tよりも厳密であるため、機能の一貫性が向上します。


uint32_t長所:

  • さまざまなアルゴリズムがこのタイプを指定します。 IMO-使用する最良の理由。
  • 正確な幅と範囲がわかっています。
  • このタイプの配列には無駄がありません。
  • unsignedオーバーフローを伴う整数演算はより予測可能です。
  • 他の言語の32ビット型の範囲と数学のより近い一致。
  • 決してパディングされません。

uint32_t短所:

  • 常に利用できるわけではありません(2018年にはほとんどありません)。
    E.g .: 8/16/32ビット整数が不足しているプラ​​ットフォーム(9/18/- 6 -bit、 others )。
    E.g .:非2の補数を使用するプラットフォーム。 古い22

uint_fast32_t長所:

  • 常に利用可能です。
    このalwaysは、新旧のすべてのプラットフォームで高速/最小タイプを使用できるようにします。
  • "Fastest" 32ビット範囲をサポートするタイプ。

uint_fast32_t短所:

  • 範囲は最小限しか知られていません。たとえば、64ビット型である可能性があります。
  • このタイプの配列は、メモリを浪費する場合があります。
  • すべての回答(最初は私も)、投稿とコメントは間違った名前uint32_fast_tを使用しました。多くの人は、このタイプを必要としないだけで使用しているようです。正しい名前さえ使用しませんでした!
  • パディング可能-(まれ)。
  • 特定のケースでは、「最速」タイプは実際には別のタイプである場合があります。したがって、uint_fast32_tは1次近似にすぎません。

最終的に、最適なものはコーディングの目標に依存します。非常に幅広いポータビリティまたは特定のニッチなパフォーマンス関数をコーディングしない限り、uint32_tを使用します


これらのタイプを使用するとき、別の問題が発生します:int/unsignedと比較したランク

おそらくuint_fastN_tは少なくともunsignedのランクになります。これは指定されていませんが、特定のテスト可能な条件です。

したがって、uintN_tuint_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型は厳密に指定されており、移植可能なコードになります。

31
chux

多くの人が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ビットintsおよびいくつかの珍しい博物館サンプルには、32未満のその他の奇妙なintサイズがあります。

UXアンサー:

  • uint32_tよりも高速かもしれませんが、uint_fast32_tは使用に時間がかかります。入力に時間がかかります。特に、Cのドキュメントでスペルとセマンティクスを検索する際に時間がかかります;-)

優雅さが重要(明らかに意見に基づく):

  • uint32_tは見た目が悪いので、多くのプログラマーが自分のu32またはuint32タイプを定義することを好みます...この観点から、uint_fast32_tは修復できないほど不器用に見えます。友人uint_least32_tなどと一緒にベンチに座っているのも驚きではありません。
25
chqrlie

理由の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_tuint_least32_tとほぼ同じです。これは通常最速であり、保証はありません(ただし、保証はありません)。

ほとんどの場合、正確なサイズを気にしないか、または正確に 32(または64、場合によっては16)ビットにするか、「ドントケア」unsigned intとにかくtypeは最速です。これはuint_fast32_tがそれほど頻繁に使用されない理由を説明します。

8
Damon

uint32_titsの範囲に使用されるという証拠を見たことはありません。代わりに、I'veuint32_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

6
Antti Haapala

いくつかの理由。

  1. 多くの人は、「高速」タイプが存在することを知りません。
  2. 入力する方が冗長です。
  3. 型の実際のサイズがわからない場合、プログラムの動作について推論するのは困難です。
  4. 標準は実際に最速でピンダウンするわけではなく、実際にどのタイプが実際に最速であるかは、コンテキストに依存する可能性があります。
  5. プラットフォームを定義する際に、プラットフォーム開発者がこれらのタイプのサイズを考慮している証拠は見ていません。たとえば、x86-64 Linuxでは、x86-64が32ビット値の高速操作をハードウェアでサポートしているにもかかわらず、「高速」タイプはすべて64ビットです。

要約すると、「高速」タイプは価値のないゴミです。特定のアプリケーションでどのタイプが最速かを本当に把握する必要がある場合は、コンパイラでコードのベンチマークを行う必要があります。

5
plugwash

正確性とコーディングの容易さの観点から、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いくつかの指示も必要です。

  • 小さい型を使用すると、多くの場合、コンパイラは1つの64ビット操作を使用して2つの32ビット操作を実行することにより、隣接する操作を巧妙に結合できます。このタイプの「貧者のベクトル化」の例は珍しくありません。たとえば、定数struct two32{ uint32_t a, b; }two32{1, 2}のようなraxに作成すると、最適化できます 単一のmov rax, 0x20001に、64ビットバージョンには2つの命令が必要です。原則として、これは隣接する算術演算(同じ演算、異なるオペランド)でも可能であるはずですが、実際にはそれを見ていません。
  • 「メモリ使用量」を低くすると、メモリやキャッシュのフットプリントが問題にならない場合でも、命令の数が少なくなることがよくあります。このタイプの型構造または配列はコピーされるため、コピーされるレジスタごとに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だけが必要です。それでも、「通過中に構造を密にパックする」設計が全体的に理にかなっていることを受け入れた場合、値が小さいほどこの機能を活用できます。

5
BeeOnRope

私の理解では、intは当初、サイズが少なくとも16ビットであることを保証する「ネイティブ」整数型であると想定されていました。当時は「合理的な」サイズと考えられていました。

32ビットプラットフォームがより一般的になったとき、「合理的な」サイズが32ビットに変更されたと言えます。

  • 最新のWindowsは、すべてのプラットフォームで32ビットintを使用します。
  • POSIXは、intが少なくとも32ビットであることを保証します。
  • C#、Javaの型はintで、正確に32ビットであることが保証されています。

しかし、64ビットプラットフォームが標準になったとき、次の理由でintを64ビット整数に拡張した人はいませんでした。

  • 移植性:多くのコードはintのサイズが32ビットであることに依存しています。
  • メモリ消費:ほとんどの場合、使用中の数は20億よりもはるかに少ないため、ほとんどの場合、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ビットアーキテクチャでは、コンパイラーは場合によっては追加の命令を発行する必要があります(上記を参照)。

4
StaceyGirl

実用的には、uint_fast32_tはまったく役に立ちません。最も広く普及しているプラ​​ットフォーム(x86_64)で誤って定義されており、非常に低品質のコンパイラがない限り、実際にはどこにも利点はありません。概念的には、データ構造/配列で「高速」タイプを使用することは決して意味がありません。より効率的に操作できるタイプから得られる節約は、サイズを大きくするコスト(キャッシュミスなど)によってd小になります。作業データセット。そして、個々のローカル変数(ループカウンター、tempsなど)の場合、通常、非おもちゃのコンパイラは生成されたコードのより大きな型でより効率的であれば動作します。署名されたタイプ、それは決して必要ではありません)。

理論的に有用なバリアントの1つはuint_least32_tです。これは、32ビット値を格納する必要があるが、正確なサイズの32ビット型を持たないマシンに移植したい場合に使用します。ただし、実際には、心配する必要はありません。

4
R..

多くの場合、アルゴリズムがデータの配列に対して機能する場合、パフォーマンスを改善する最善の方法は、キャッシュミスの数を最小限にすることです。各要素が小さいほど、より多くの要素をキャッシュに収めることができます。これが、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型をまったく持たない実装は、標準に正式に準拠して宣言することさえできます! (多くの既存のプログラムをコンパイルすることはできません。)実際には、intuint32_t、およびuint_least32_tが同じではなく、利点がないアーキテクチャはありません現在 =、uint_fast32_tのパフォーマンス。では、なぜ物事を複雑にしすぎているのでしょうか?

しかし、既にlongがあったときにすべての32_t型が存在する必要があった理由を見てください。これらの仮定は以前に私たちの顔で爆発したことがわかります。正確な幅の32ビット計算がネイティブのWordサイズより遅いマシンでコードがいつか実行される可能性があり、ストレージにuint_least32_tを、計算にuint_fast32_tを宗教的に使用した方が良いでしょう。または、その橋に着いたときにその橋を渡り、単純なものが必要な場合は、unsigned longがあります。

2
Davislor

直接答える: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のために、ほとんどの場合、短いタイプが単純に勝つ理由です。

1
Marc Lehmann