uint_fast32_tよりもuint32_tが優先されるのはなぜですか?


82

それuint32_tよりもはるかに普及しているようですuint_fast32_t(これは事例証拠であると私は理解しています)。しかし、それは私には直感に反しているようです。

ほとんどの場合、実装の使用を見ると、uint32_t本当に必要なのは、最大4,294,967,295(通常は65,535から4,294,967,295の間のはるかに低い境界)の値を保持できる整数です。

その後、使用する奇妙なようだuint32_tとして、「正確に32ビットの」保証が必要とされていない、と「最速の利用可能> = 32ビット」の保証はuint_fast32_t正確に正しい考えているようです。さらに、通常は実装されていますが、uint32_t実際に存在することが保証されているわけではありません。

では、なぜそれuint32_tが好まれるのでしょうか。それは単によく知られているのですか、それとも他のものよりも技術的な利点がありますか?


24
簡単な答えですが、正確に32ビットの整数が必要なのかもしれません。
Stargateur 2017年

7
最初に聞いたことがありますがuint32_fast_t、正しく理解していれば、少なくとも32ビットです(つまり、それ以上になる可能性がありますか?誤解を招くように聞こえます)。uint32_tこのデータをまとめてネットワーク経由で送信しているため、現在プロジェクトで友人を使用しています。送信者と受信者にフィールドの大きさを正確に知らせたいと考えています。プラットフォームが実装されていない可能性があるため、このようなソリューションは最も堅牢なソリューションではないかもしれませんuint32_tが、私のすべてが明らかに実装しているので、私は自分がやっていることで大丈夫です。
矢野2017年

5
@yano:ネットワーキングの場合、バイトの順序/エンディアンにも注意する必要があります-それuint32_tは与えられません(uint32_t_beそしてuint32_t_leuint32_t現在最良のオプションであるほとんどすべての可能なケースに適している、とがないのは残念です)。
ブレンダン

3
@ Brendan-_beと_leに関して、htonl()とntohl()は同じ機能を提供しますか?
mpez0 2017年

2
@Brendanは、すべてプリミティブ型である標準のintに隠すのにかなり重いオブジェクトです。私は原則として、これはどこかの標準で処理されるべきであることに同意しますが、これはその場所ではないかもしれないと思います
Steve Cox

回答:


79

uint32_tそれをサポートするすべてのプラットフォームでほぼ同じプロパティを持つことが保証されています。1

uint_fast32_t 比較すると、さまざまなシステムでどのように動作するかについての保証はほとんどありません。

uint_fast32_tサイズが異なるプラットフォームに切り替える場合は、を使用するすべてのコードをuint_fast32_t再テストして検証する必要があります。すべての安定性の仮定は、ウィンドウの外になります。システム全体の動作は異なります。

コードを書くとき、32ビットサイズでないシステムにアクセスすることさえできないかもしれませんuint_fast32_t

uint32_t 動作が異なりません(脚注を参照)。

正確さは速度よりも重要です。したがって、時期尚早の正確さは、時期尚早の最適化よりも優れた計画です。

uint_fast32_t64ビット以上のシステム用のコードを書いていた場合は、両方のケースでコードをテストして使用する可能性があります。必要性と機会の両方を除けば、そうすることは悪い計画です。

最後に、uint_fast32_t任意の時間またはインスタンス数で保存する場合、uint32キャッシュサイズの問題とメモリ帯域幅が原因であるよりも遅くなる可能性があります。今日のコンピューターは、CPUにバインドされているよりもはるかに多くの場合メモリにバインドされておりuint_fast32_t、メモリのオーバーヘッドを考慮した後ではなく、単独で高速になる可能性があります。


1 @chuxがコメントで指摘しているように、unsignedより大きい場合uint32_t、算術演算uint32_tは通常の整数昇格を通過し、そうでない場合は、のままになりますuint32_t。これはバグを引き起こす可能性があります。完璧なものはありません。


15
「uint32_tは、それをサポートするすべてのプラットフォームで同じプロパティを持つことが保証されています。」がunsignedより広い場合uint32_tuint32_tあるプラットフォームでは通常の整数プロモーションを実行し、別のプラットフォームでは実行しない場合、コーナーの問題が発生します。しかし、uint32_tこれらの整数数学の問題は大幅に軽減されます。
chux -復活モニカ

2
@chuxは、乗算時にUBを引き起こす可能性のあるコーナーケースです。これは、プロモーションが符号付き整数を優先、符号付き整数オーバーフローがUBであるためです。
CodesInChaos 2017年

2
この答えはそれに関しては正しいですが、重要な詳細を非常に軽視しています。一言で言えば、uint32_tはタイプのマシン表現の正確な詳細が重要であるのに対し、uint_fast32_tは計算速度が最も重要であり、(符号なし)と最小範囲が重要であり、表現の詳細は必須ではありません。ありますuint_least32_t(UN)符号の有無と最小範囲が最も重要であるところ、コンパクトさがスピードよりも重要である、と正確な表現は必須ではないため。
ジョンボリンジャー2017年

@JohnBollingerこれはすべてうまくいっていますが、複数のバリアントを実装する実際のハードウェアでテストしないと、可変サイズタイプはトラップになります。そして、人々uint32_tが他のタイプではなく使用する理由は、彼らが通常テストを行うためのそのようなハードウェアを持っていないからです。(同じことがint32_tより少ない程度で、さらにはintそしてshort)にも当てはまります。
Yakk-Adam Nevraumont 2017年

1
コーナーケースの例:unsigned short==uint32_tおよびint==としint48_tます。のようなものを計算すると(uint32_t)0xFFFFFFFF * (uint32_t)0xFFFFFFFF、オペランドはに昇格しsigned int、符号付き整数オーバーフローをトリガーします。これは未定義の動作です。この質問を参照してください。
ナユキ2017年

32

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

注:誤った名前uint32_fast_tuint_fast32_t。にする必要があります。

uint32_tはより厳密な仕様であるuint_fast32_tため、より一貫性のある機能になります。


uint32_t 長所:

  • さまざまなアルゴリズムがこのタイプを指定します。IMO-使用する最良の理由。
  • 既知の正確な幅と範囲。
  • このタイプのアレイは無駄がありません。
  • オーバーフローのある符号なし整数演算は、より予測可能です。
  • 他の言語の32ビットタイプの範囲と数学のより厳密な一致。
  • パディングしないでください。

uint32_t 短所:

  • 常に利用できるとは限りません(ただし、2018年にはこれはまれです)。
    例えば、8/16/32ビット整数(9/18 /欠くプラットフォーム36ビット、その他を)。
    例:2以外の補数を使用するプラットフォーム。古い2200

uint_fast32_t 長所:

  • いつでもご利用いただけます。
    これにより、新旧を問わず、すべてのプラットフォームで高速/最小タイプを常に使用できます。
  • 32ビット範囲をサポートする「最速」タイプ。

uint_fast32_t 短所:

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

結局、何が最善かはコーディングの目標によって異なります。非常に広い移植性またはニッチなパフォーマンス関数をコーディングしない限り、を使用してくださいuint32_t


関係するこれらのタイプを使用する場合、別の問題があります。 int/unsigned

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

したがって、より狭くなるuintN_t可能性が高くなります。これは、数学を使用するコードは、移植性に関する場合よりも整数昇格の対象となる可能性が高いことを意味します。uint_fastN_tunsigneduintN_tuint_fastN_t

この懸念事項:uint_fastN_t選択した数学演算による移植性の利点。


int32_tではなくについての補足int_fast32_t:まれなマシンでINT_FAST32_MINは、-2,147,483,648ではなく-2,147,483,647になる場合があります。より大きなポイント:(u)intN_tタイプは厳密に指定されており、移植可能なコードにつながります。


2
32ビット範囲をサポートする最速のタイプ=>本当に?これは、RAMがCPU速度で実行されていた時代の遺物です。現在、PCのバランスは劇的に変化しているため、(1)メモリから32ビット整数をプルする速度は64ビット整数をプルする速度の2倍であり、(2)ベクトル化された命令です。 32ビット整数では、64ビット整数の2倍のクランチが発生します。それでも本当に最速ですか?
Matthieu M.

4
あるものでは最も速く、他のものでは遅い。 配列とゼロ拡張の必要性を考慮すると、「整数の最速サイズは何ですか」に対する万能の答えはありません。 x86-64 System V ABIではuint32_fast_t64ビットタイプであるため、時折発生する符号拡張を節約し、imul rax, [mem]64ビット整数またはポインターで使用する場合に個別のゼロ拡張ロード命令の代わりに使用できます。しかし、それはあなたが二重のキャッシュ・フットプリントと余分なコードサイズの価格で取得するすべてです(REXはすべてに接頭辞。)
ピーター・コルド

1
また、64ビット除算はほとんどのx86 CPUで32ビット除算よりもはるかに遅く、一部(Bulldozerファミリ、Atom、Silvermontなど)では32よりも64ビット乗算が遅くなります。Bulldozerファミリも64ビットが遅くなります。popcnt。また、このタイプは32ビット値にのみ安全であることに注意してください。他のアーキテクチャでは小さいため、このコストを無料で支払うことになります。
Peter Cordes 2017年

2
すべてのCおよびC ++アプリケーションの加重平均として、uint32_fast_tx86での作成はひどい選択だと思います。より高速な操作は少なく、これまでの間、及びそれらが発生恩恵はほとんどが非常に少量です:用の違いimul rax, [mem]@PeterCordesが言及している場合は、非常に非常に小さな:融合していないドメインの融合ドメインとゼロで単一のuop。最も興味深いシナリオでは、単一のサイクルを追加することすらありません。これと、メモリ使用量の2倍、およびベクトル化の悪化とのバランスをとると、頻繁に勝つことはわかりません。
BeeOnRope 2017年

2
@ PeterCordes-興味深いがひどい:)。それはfast_tさらに悪化するでしょうint:それは異なるプラットフォームで異なるサイズを持っているだけでなく、最適化の決定と異なるファイルでの異なるサイズに応じて異なるサイズを持っているでしょう!実際問題として、プログラム全体の最適化でも機能しないと思います。CとC ++のサイズは固定されているためsizeof(uint32_fast_t)、直接決定するものは常に同じ値を返す必要があるため、コンパイラが行うのは非常に困難です。そのような変換を行います。
beeOnRope 2017年

25

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

愚かな答え:

  • 標準タイプはありません。uint32_fast_t正しいスペルは次のとおりです。uint_fast32_t。です。

実用的な答え:

  • 多くの人が実際に、uint32_tまたはint32_t正確なセマンティクスのために、符号なしラップアラウンド算術(uint32_t)または2の補数表現(int32_t)を使用した正確な32ビットを使用しています。ザ・xxx_fast32_tタイプは、バイナリファイルに格納するために大きく、したがって、不適切であるパックアレーと構造における使用、またはネットワークを介して送信することができます。さらに、それらはさらに速くないかもしれません。

実用的な答え:

  • uint_fast32_tコメントと回答に示されているように、多くの人は、について知らない(または単に気にしない)だけで、おそらくunsigned int同じセマンティクスを持っていると単純に想定していますが、現在の多くのアーキテクチャにはまだ16ビットがありますintがあり、いくつかの珍しい博物館のサンプルには32未満の他の奇妙なintサイズ。

UXの答え:

  • おそらくより高速ですがuint32_tuint_fast32_t使用には時間がかかります。特に、Cドキュメントでスペルとセマンティクスを検索する場合は、入力に時間がかかります;-)

エレガンスが重要です(明らかに意見に基づいています):

  • uint32_t多くのプログラマーが自分自身u32uint32タイプを定義することを好むほど悪く見えます...この観点からは、uint_fast32_t修復できないほど不器用に見えます。友達uint_least32_tなどと一緒にベンチに座っているのも当然です。

UXの場合は+1。std::reference_wrapper思ったよりはましですが、標準委員会が標準化したタイプの使用を本当に望んでいるのではないかと思うことがあります...
Matthieu M.

7

1つの理由は、unsigned int特別なtypedefや何かを含める必要がなく、すでに「最速」であるということです。したがって、高速で必要な場合は、基本intまたはunsigned intタイプを使用してください。
この標準は、最速であることを明示的に保証していませんが、 3.9.1で「プレーンintは、実行環境のアーキテクチャによって提案された自然なサイズを持っている間接的に保証しています。言い換えると、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標準では、「通常は最速の整数型を指定する」(「通常」という単語に注意)と記載されており、すべての目的で最速である必要はないことが明示されています。言い換えれば、uint_fast32_tuint_least32_t通常とほぼ同じです最速ですが、保証はありません(ただし、どちらの方法でも保証はありません)。

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


3
int8ビットプロセッサで16ビットを覚えていないことに驚いています。当時からもっと大きなものを使っていたものは覚えていません。メモリが機能する場合、セグメント化されたx86アーキテクチャのコンパイラintも16ビットを使用しました。
マークランサム2017年

@MarkRansom:うわー、その通りです。私はそれintが68000の32ビットであるとすっごく確信しました(例として私が考えた)。それは...ありませんでした
デイモン

int最小幅が16ビットの過去最速のタイプであることが意図されていましたが(これがCに整数昇格ルールがある理由です)、現在64ビットアーキテクチャではこれはもはや当てはまりません。たとえば、8バイト整数はx86_64ビットの4バイト整数よりも高速です。これは、4バイト整数では、コンパイラが他の8バイト値と比較する前に4バイト値を8バイト値に展開する追加の命令を挿入する必要があるためです。
StaceyGirl 2017年

「unsignedint」は、x64で必ずしも最速であるとは限りません。奇妙なことが起こった。
ジョシュア

もう1つの一般的なケースはlong、歴史的な理由から、32ビットであるint必要があり、現在はより広くする必要がないlongためint、64ビットの方が速い場合でも32ビットのままにする必要がある場合があります。
Davislor 2018

6

私はその範囲にuint32_t使用される証拠を見たことがありません。代わりに、私が見たほとんどの場合、ラップアラウンドとシフトのセマンティクスが保証された、さまざまなアルゴリズムで正確に4オクテットのデータを保持することです。uint32_t

uint32_t代わりに使用する他の理由もありますuint_fast32_t:多くの場合、安定したABIを提供するためです。さらに、メモリ使用量を正確に知ることができます。これはuint_fast32_t、そのタイプがのタイプと異なる場合は常に、からの速度ゲインが何であれ、非常にオフセットしuint32_tます。

65536未満の値の場合、すでに便利なタイプがあり、呼び出されますunsigned intunsigned short少なくともその範囲も必要ですunsigned intが、ネイティブワードサイズです)4294967296未満の値の場合、と呼ばれる別のタイプがありunsigned longます。


そして最後に、uint_fast32_tタイプするのが面倒で長く、タイプミスしやすいので、人々は使用しません:D


@ikegami:あなたはshort編集で私の意図を変えました。とは異なる場合int、おそらく高速shortです。
Antti Haapala 2017年

1
それなら、あなたの最後の文は完全に間違っています。unsigned int代わりに使用uint16_fast_tする必要があると主張することは、コンパイラよりもよく知っていると主張することを意味します。
ikegami 2017年

また、あなたのテキストの意図を変えてしまったことをお詫びします。それは私の意図ではありませんでした。
ikegami 2017年

unsigned longプラットフォームに64ビットlongがあり、必要なのは数字だけの場合は、適切な選択ではありません<2^32
ruslan

1
@ikegami:タイプ「unsignedint」は、プロモートされている場合でも、常にunsignedタイプとして動作します。この点で、uint16_tとの両方よりも優れていuint_fast16_tます。場合はuint_fast16_t、より緩く、通常の整数型よりも指定されていたように、その範囲の必要性は、内部で32ビット演算を実行するプラットフォーム上でいくつかのパフォーマンス上の利点を提供しますが、16ビットのデータバスを持つことができ、アドレスが取られていないオブジェクトに対して一貫していないこと。ただし、この規格ではそのような柔軟性は認められていません。
スーパーキャット2017年

5

いくつかの理由。

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

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


歴史的に、32ビットおよび/または64ビットのメモリアクセス命令を備えていたが、8ビットおよび16ビットを備えていないプロセッサがありました。したがって、int_fast {8,16} _tは、20年以上前にはまったく愚かではなかったでしょう。最後のそのような主流のプロセッサは、オリジナルのDEC Alpha 21064でした(第2世代の21164が改良されました)。おそらく、組み込みのDSPや、ワードアクセスのみを行うものはまだ存在しますが、移植性は通常、そのようなことに関して大きな懸念事項ではないため、なぜそれらを高速でカーゴカルトするのかわかりません。そして、手作りのCray「すべてが64ビット」のマシンがありました。
user1998586 2017年

1
カテゴリ1b:多くの人は「速い」タイプが存在することを気にしません。それが私のカテゴリーです。
gnasher729 2017年

カテゴリ6:多くの人は「速い」タイプが最速であるとは信じていません。私はそのカテゴリーに属しています。
2017年

5

上記の多くのユーザーが指摘しているように、コーディングの正確さと容易さの観点から、特により正確に定義されたサイズと算術セマンティクスのために、uint32_t多くの利点uint_fast32_tがあります。

おそらく見逃されているのは、利点として想定されuint_fast32_tていることです。それは、より高速であり、意味のある方法で実現されることは決してないということです。64ビット時代を支配してきた64ビットプロセッサのほとんど(主にx86-64とAarch64)は、32ビットアーキテクチャから進化し、64ビットモードでも高速の32ビットネイティブ操作を備えています。したがって、これらのプラットフォームuint_fast32_tとまったく同じuint32_tです。

POWER、MIPS64、SPARCなどの「実行された」プラットフォームの一部が64ビットALU操作のみを提供している場合でも、興味深い32ビット操作の大部分は64ビットレジスタで問題なく実行できます。下部の32ビットは望ましい結果が得られます(そして、すべての主流プラットフォームでは、少なくとも32ビットをロード/保存できます)。左シフトが主な問題ですが、それでも多くの場合、コンパイラーの値/範囲追跡の最適化によって最適化できます。

最もあいまいなアプリケーションを除いて、時折わずかに遅い左シフトまたは32x32-> 64乗算が、そのような値のメモリ使用量の2倍を上回るとは思えません。

最後に、トレードオフは主に「メモリの使用とベクトル化の可能性」(に賛成uint32_t)と命令数/速度(に賛成uint_fast32_t)として特徴付けられていますが、それでも私にはわかりません。はい、一部のプラットフォームでは、一部の32ビット操作に追加の命令が必要になりますが、次の理由でいくつかの命令も保存します。

  • 小さいタイプを使用すると、コンパイラーは、1つの64ビット操作を使用して2つの32ビット操作を実行することにより、隣接する操作を巧みに組み合わせることができます。このタイプの「貧乏人のベクトル化」の例は珍しいことではありません。たとえば、定数struct two32{ uint32_t a, b; }raxlikeに作成することは単一にtwo32{1, 2} 最適化できますがmov rax, 0x20001、64ビットバージョンには2つの命令が必要です。原則として、これは隣接する算術演算(同じ演算、異なるオペランド)でも可能であるはずですが、実際には見たことがありません。
  • 「メモリ使用量」が少ないと、メモリまたはキャッシュのフットプリントが問題にならない場合でも、多くの場合、命令が少なくなります。このタイプの構造体または配列がコピーされるため、コピーされるレジスタごとに2倍の価値が得られます。
  • データ型が小さいほど、データ構造データをレジスタに効率的にパックするSysVABIなどの最新の呼び出し規約を利用することがよくあります。たとえば、レジ​​スタで最大16バイトの構造体を返すことができますrdx:rax。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`タイプがインターフェースをとる必要がある場所では、(開発の複雑さに加えて)不要な記号拡張やその他のサイズの不一致に関連するコードが見つかるかもしれません。多くの場合、コンパイラーはこれを最適化することで問題ありませんが、異なるサイズのタイプを混合するときに最適化された出力でこれを確認することは珍しくありません。

上記の例のいくつかや、godboltでさらに遊ぶことができます。


1明確にするために、構造体をレジスターに密にパックするという規則は、小さい値に対して常に明確な勝利とは限りません。これは、小さい値を使用する前に「抽出」する必要がある場合があることを意味します。たとえば、2つの構造体メンバーの合計を返す単純な関数mov rax, rdi; shr rax, 32; add edi, eaxは、64ビットバージョンの場合、各引数が独自のレジスタを取得し、単一のaddまたはを必要とするだけleaです。それでも、「通過中に構造を密に詰める」設計が全体的に理にかなっていることを受け入れる場合は、値を小さくすると、この機能をより活用できます。


x86-64 Linux上のglibcは64ビットを使用しますがuint_fast32_t、これはIMOの間違いです。(明らかに、Windowsuint_fast32_tはWindowsでは32ビットタイプです。)x86-64 Linuxでは64ビットであるため、誰も使用することをお勧めしません。uint_fast32_t命令数が少なくなるように最適化されています(関数引数と戻り値はゼロ拡張子を必要としません。主要な重要なプラットフォームの1つで、全体的な速度やコードサイズではなく、配列インデックスとして使用します。
ピーターコーデス2017年

2
そうです、SysV ABIについての上記のコメントを読みましたが、後で指摘するように、それを決定したのは別のグループ/ドキュメントであった可能性があります-しかし、一度それが起こると、それはほとんど石になっていると思います。コンパイラが小さいタイプをより適切に最適化できる場合がまだあるため、純粋なサイクルカウント/命令カウントがメモリフットプリントの影響とベクトル化を無視しても大きいタイプを優先することは疑わしいと思います。上記にいくつかの例を追加しました。@PeterCordes
BeeOnRope

複数の構造体メンバーを同じレジスターにパックするSysVは、またはを返すときにかなり頻繁に多くの命令を消費します。両方のメンバーがコンパイル時定数でない場合、通常は単なるORだけではなく、呼び出し元は戻り値を解凍する必要があります。(bugs.llvm.org/show_bug.cgi?id=34840 LLVMは、プライベート機能のために通過する戻り値を最適化し、そして全体を取るように、32ビットのintを扱うべきように分離されている代わりに、64ビットの定数を必要としますit。)pair<int,bool>pair<int,int>raxbooldltest
Peter Cordes 2017年

1
コンパイラは一般的に関数を分割しないと思います。ファストパスを個別の関数としてピーリングすることは、ソースレベルの最適化に役立ちます(特に、インライン化できるヘッダーで)。入力の90%が「何もしない場合」である場合、非常に良い可能性があります。発信者のループでそのフィルタリングを行うことは大きなメリットです。IIRC、Linuxは__attribute__((noinline))、gccがエラー処理関数をインライン化しないことを正確に確認し、多くの呼び出し元があり、それ自体がインライン化されていないいくつかの重要なカーネル関数の高速パスにpush rbx/ .../ pop rbx/ ...の束を配置します。
ピーターコーデス2017年

1
Javaでは、インライン化がさらなる最適化(特にC ++とは異なり普及している非仮想化)にとって非常に重要であるため、それも非常に重要です。そのため、そこで高速パスを分割することはしばしば報われます。「バイトコードの最適化」は実際には重要です(にもかかわらず)インライン化の決定はインライン化されたマシンコードサイズではなくバイトコードサイズに基づいているため、バイトコードのカウントダウンを取得するためだけにJITが最終コンパイルを行うため、意味がないという従来の知識(および相関は桁数によって異なる可能性があります)。
BeeOnRope 2017年

4

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

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


4

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

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

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

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

  • 移植性:多くのコードはint、サイズが32ビットであることに依存しています。
  • メモリ消費量:intほとんどの場合、使用されている数は20億よりはるかに少ないため、すべてのメモリ使用量を2倍にすることはほとんどの場合不合理かもしれません。

さて、なぜあなたが好むだろうuint32_tuint_fast32_t?同じ理由で、C#とJavaは常に固定サイズの整数を使用します。プログラマーは、さまざまなタイプの可能なサイズを考慮してコードを記述せず、1つのプラットフォーム用に記述し、そのプラットフォームでコードをテストします。ほとんどのコードは、データ型の特定のサイズに暗黙的に依存しています。そして、これがuint32_tほとんどの場合に良い選択である理由です-それはその振る舞いに関して曖昧さを許しません。

さらに、uint_fast32_t32ビット以上のサイズのプラットフォームで本当に最速のタイプですか?あんまり。Windows上のx86_64用のGCCによるこのコードコンパイラを検討してください。

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

mov %eax,%eax32ビット値を64ビット値に拡張することを目的とした関数呼び出し後の追加の命令を除いて、生成されたコードがほぼ同じであることに注意してください。

32ビット値のみを使用する場合はそのような問題はありませんが、おそらくsize_t変数付きの値(おそらく配列サイズ?)を使用し、x86_64では64ビットです。Linuxではuint_fast32_t8バイトなので、状況は異なります。

多くのプログラマーはint、小さな値を返す必要があるときに使用します(たとえば、[-32,32]の範囲)。これはint、プラットフォームのネイティブ整数サイズの場合は完全に機能しますが、64ビットプラットフォームではないため、プラットフォームのネイティブタイプと一致する別のタイプの方が適しています(小さいサイズの他の整数で頻繁に使用される場合を除く)。

基本的に、標準が何を言っているかに関係なく、uint_fast32_tとにかくいくつかの実装では壊れています。一部の場所で生成される追加の命令が気になる場合は、独自の「ネイティブ」整数型を定義する必要があります。またはsize_t、通常はnativeサイズと一致するため、この目的で使用できます(8086のような古くてあいまいなプラットフォームは含まれていません。Windows、Linuxなどを実行できるプラットフォームのみが含まれています)。


intネイティブ整数型であると想定されていたことを示すもう1つの記号は、「整数昇格規則」です。ほとんどのCPUはネイティブでのみ操作を実行できるため、32ビットCPUは通常、32ビットの加算、減算などしか実行できません(Intel CPUはここでは例外です)。他のサイズの整数型は、ロードおよびストア命令によってのみサポートされます。たとえば、8ビット値は適切な「8ビット符号付きロード」または「8ビット符号なしロード」命令でロードする必要があり、ロード後に値を32ビットに拡張します。整数昇格規則がないと、Cコンパイラーは、ネイティブ型よりも小さい型を使用する式用にもう少しコードを追加する必要があります。残念ながら、これは64ビットアーキテクチャではもはや当てはまりません。コンパイラが場合によっては追加の命令を発行する必要があるためです(上記のとおり)。


2
「intを64ビット整数に拡張した人はいない」と「残念ながら、これは64ビットアーキテクチャではもう当てはまらない」という考えは非常に良い点です。「最速」とアセンブリコードの比較について公平を期すために:この場合、2番目のコードスニペットは追加の命令で遅いように見えますが、コードの長さと速度はあまり相関していない場合があります。より強力な比較では、実行時間が報告されますが、それはそれほど簡単ではありません。
chux -復活モニカ

2番目のコードの速度を測定するのは簡単ではありません。IntelCPUは本当に良い仕事をしているかもしれませんが、コードが長いとキャッシュの汚染も大きくなります。たまに1つの命令を実行しても問題はないかもしれませんが、uint_fast32_tの有用性はあいまいになります。
StaceyGirl 2017年

uint_fast32_t非常に選択された状況を除いて、の有用性があいまいになることについて強く同意します。駆動の理由 uint_fastN_tは、「unsignedコードが多すぎると壊れるので、新しいプラットフォームでは最速になることが多いのに、64ビットとして使用しない」に対応するためだと思いますが、「少なくともNビットタイプの高速が必要です。 。」できればまたUVします。
chux -復活モニカ

ほとんどの64ビットアーキテクチャは、32ビット整数で簡単に操作できます。DEC Alpha(PowerPC64やMIPS64のような既存の32ビットISAの拡張ではなく、ブランチの新しい64ビットアーキテクチャ)でさえ、32ビットと64ビットのロード/ストアがありました。(ただし、バイトまたは16ビットのロード/ストアではありません!)。ほとんどの命令は64ビットのみでしたが、32ビットのadd / subおよび乗算に対するネイティブHWサポートがあり、結果を32ビットに切り捨てました。(alasir.com/articles/alpha_history/press/alpha_intro.html)したがって、int64ビットを作成しても速度はほとんど向上せず、通常はキャッシュフットプリントによる速度の低下はありません。
ピーター

また、int64ビットを作成した場合、uint32_t固定幅のtypedefには、__attribute__またはその他のハック、またはint。よりも小さいカスタムタイプが必要になります。(またはshort、しかし、あなたは同じ問題を抱えていuint16_tます。)誰もそれを望んでいません。32ビットはほとんどすべてに十分な幅です(16ビットとは異なります)。必要なのが32ビット整数である場合に32ビット整数を使用することは、64ビットマシンで意味のある方法で「非効率的」ではありません。
ピーターコーデス

2

多くの場合、アルゴリズムがデータの配列で機能する場合、パフォーマンスを向上させる最善の方法は、キャッシュミスの数を最小限に抑えることです。各要素が小さいほど、より多くの要素をキャッシュに収めることができます。これが、64ビットマシンで32ビットポインターを使用するように多くのコードがまだ記述されている理由です。4GiBに近いデータは必要ありませんが、すべてのポインターとオフセットを作成するコストには、4バイトではなく8バイトが必要です。かなりのものになるでしょう。

IPv4アドレスなど、正確に32ビットを必要とするように指定されたABIやプロトコルもいくつかあります。それがuint32_t本当の意味です。CPUで効率的かどうかに関係なく、正確に32ビットを使用してください。これらは以前はlongまたはとして宣言されてunsigned longいたため、64ビットの移行中に多くの問題が発生しました。少なくとも2³²-1までの数値を保持する符号なし型が必要な場合はunsigned long、最初のC標準が発表されて以来の定義です。ただし、実際には、十分な古いコードは、longが任意のポインターまたはファイルのオフセットまたはタイムスタンプを保持できると想定し、十分な古いコードは、正確に32ビット幅であると想定しているためlong、コンパイラーは必ずしも同じものを作成できるとは限りません。int_fast32_t多くのものを壊さずにを。

理論的には、プログラムで使用する方が将来性が高くuint_least32_tuint_least32_t要素をにロードすることもできます。uint_fast32_t計算のために変数にできます。uint32_tタイプがまったくない実装は、標準に正式に準拠していると宣言することさえできます。(それはちょうど、多くの既存のプログラムをコンパイルすることができません。)実際には、そこにはアーキテクチャがこれ以上どこませんintuint32_tuint_least32_t同じではない、と何の利点、現在の性能にuint_fast32_t。では、なぜ物事を複雑にしすぎるのでしょうか。

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


ただしint、ILP64など、32ビットではないアーキテクチャがあります。それらが一般的であるというわけではありません。
Antti Haapala 2017年

ILP64は現在形では存在しないと思いますか?いくつかのウェブページは「Cray」がそれを使用していると主張しており、そのすべてが1997年の同じUnix.orgページを引用していますが、90年代半ばのUNICOSは実際にはもっと奇妙なことをし、今日のCraysはIntelハードウェアを使用しています。その同じページは、ETAスーパーコンピューターがILP64を使用したと主張していますが、それらはずっと前に廃業しました。ウィキペディアは、HALのSolarisからSPARC64への移植でILP64を使用したと主張していますが、それらも何年もの間廃業しています。CppReferenceによると、ILP64はいくつかの初期の64ビットユニスでのみ使用されていました。したがって、これは非常に難解なレトロコンピューティングにのみ関係します。
Davislor 2017年

今日IntelのMathKernel Libraryの「ILP64インターフェイス」を使用する場合、int32ビット幅になることに注意してください。タイプMKL_INTは何が変わるかです。
Davislor 2017年

1

直接的な答えを与えるために:私は、uint32_t使用されている、uint_fast32_tまたはuint_least32_t単に入力しやすいという本当の理由だと思います。短いため、読みやすくなっています。いくつかのタイプで構造体を作成し、その一部がuint_fast32_tまたは同様の場合、非常に短いCのintまたはboolまたは他のタイプとうまく整列させるのが難しいことがよくあります(適切な例:charvs character。)。もちろん、これをハードデータでバックアップすることはできませんが、他の回答は理由を推測することしかできません。

技術的な理由としてはuint32_t、何もないと思います。正確な32ビットのunsigned intが絶対に必要な場合は、このタイプが唯一の標準化された選択肢です。他のほとんどすべての場合、技術的には他のバリエーションが望ましいuint_fast32_tです。具体的には、速度uint_least32_tが心配な場合や、ストレージスペースが心配な場合です。uint32_tこれらのいずれかの場合に使用すると、型が存在する必要がないため、コンパイルできないリスクがあります。

実際には、uint32_tおよび関連するタイプは、一部の非常にまれな(現在の)DSPまたはジョークの実装を除いて、現在のすべてのプラットフォームに存在するため、正確なタイプを使用する実際のリスクはほとんどありません。同様に、固定幅タイプでは速度のペナルティに遭遇する可能性がありますが、それらは(最新のCPUでは)もはや機能しません。

だからこそ、プログラマーの怠惰さのために、ほとんどの場合、短いタイプが勝つだけだと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.