符号付き整数に符号なし整数を使用することにより、パフォーマンスの向上/損失はありますか?
もしそうなら、これは同様に短くも長くも続きますか?
2の累乗による除算は、unsigned int
、単一のシフト命令に最適化できるため。 signed int
、除算はゼロに向かってを丸めますが、右にシフトするとdownになるため、通常はより多くの機械語命令が必要です。例:
int foo(int x, unsigned y)
{
x /= 8;
y /= 8;
return x + y;
}
関連するx
部分(符号付き除算)は次のとおりです。
movl 8(%ebp), %eax
leal 7(%eax), %edx
testl %eax, %eax
cmovs %edx, %eax
sarl $3, %eax
関連するy
部分(符号なし除算)は次のとおりです。
movl 12(%ebp), %edx
shrl $3, %edx
C++(およびC)では、符号付き整数オーバーフローは未定義ですが、符号なし整数オーバーフローはラップアラウンドするように定義されています。たとえば、 gccでは、-fwrapvフラグを使用して、符号付きオーバーフローを定義できます(ラップアラウンドするため)。
未定義の符号付き整数オーバーフローにより、コンパイラはオーバーフローが発生しないと想定でき、最適化の機会がもたらされる可能性があります。例参照 このブログ投稿 議論のため。
unsigned
は、signed
と同等以上のパフォーマンスにつながります。いくつかの例:
signed
数値に対して4つの命令を実装する理由がわかりません。gccはunsigned
の場合と同様に1つの命令で実行します)short
は通常、int
(sizeof(short) < sizeof(int)
と仮定)と同じかそれより悪いパフォーマンスにつながります。プロセッサのレジスタに格納されているint
型の変数に算術演算の結果(通常はshort
、決してshort
)を割り当てると、パフォーマンスが低下します(これもint
型です)。 short
からint
へのすべての変換には時間がかかり、面倒です。
注:一部のDSPには、signed short
タイプ;この特定の場合、short
はint
よりも高速です。
int
とlong
の違いについては、推測することしかできません(64ビットアーキテクチャに精通していません)。もちろん、int
とlong
のサイズが同じ場合(32ビットプラットフォーム)、それらのパフォーマンスも同じです。
いくつかの人々が指摘した非常に重要な追加:
ほとんどのアプリケーションで本当に重要なのは、メモリフットプリントと使用帯域幅です。必要な最小の整数(short
、場合によってはsigned/unsigned char
)大きな配列の場合。
これによりパフォーマンスは向上しますが、ゲインは非線形(つまり、2倍または4倍ではない)であり、多少予測不可能です-キャッシュサイズと、アプリケーションの計算とメモリ転送の関係に依存します。
これは正確な実装に依存します。ただし、ほとんどの場合、違いはありません。本当に気にするのであれば、考慮したすべてのバリエーションを試して、パフォーマンスを測定する必要があります。
これは、特定のプロセッサに大きく依存しています。
ほとんどのプロセッサには、符号付きと符号なしの両方の算術演算のための命令があります。そのため、符号付き整数と符号なし整数の使用の違いは、コンパイラが使用するものになります。
2つのうちのいずれかが高速である場合、それは完全にプロセッサ固有であり、ほとんどの場合、違いはごくわずかです。
符号付き整数と符号なし整数のパフォーマンスの違いは、実際に受け入れられる答えが示唆するよりも一般的です。任意の定数による符号なし整数の除算は、定数が2のべき乗であるかどうかに関係なく、定数による符号付き整数の除算よりも高速にすることができます。 http://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html を参照してください
投稿の最後に、次のセクションが含まれています。
自然な問題は、同じ最適化で符号付き除算を改善できるかどうかです。残念ながら、次の2つの理由で、そうではないようです。
配当の増分は、大きさの増加、つまりn> 0の場合は増分、n <0の場合は減分でなければなりません。これにより追加の費用が発生します。
非協力的な除数のペナルティは、符号付き除算の場合の約半分に過ぎず、改善の余地は小さくなります。
したがって、切り捨てられたアルゴリズムは符号付き除算で機能するようにできますが、標準の切り上げアルゴリズムよりもパフォーマンスが低いと思われます。
2の累乗による除算が符号なし型の場合に高速であるだけでなく、他の値による除算も符号なし型の場合に高速になります。 Agner Fog's Instruction tables を見ると、符号なしの部門は符号付きのバージョンと同等以上のパフォーマンスを持っていることがわかります。
たとえば、AMD K7
╔═════════════╤══════════╤═════╤═════════╤═══════════════════════╗
║ Instruction │ Operands │ Ops │ Latency │ Reciprocal throughput ║
╠═════════════╪══════════╪═════╪═════════╪═══════════════════════╣
║ DIV │ r8/m8 │ 32 │ 24 │ 23 ║
║ DIV │ r16/m16 │ 47 │ 24 │ 23 ║
║ DIV │ r32/m32 │ 79 │ 40 │ 40 ║
║ IDIV │ r8 │ 41 │ 17 │ 17 ║
║ IDIV │ r16 │ 56 │ 25 │ 25 ║
║ IDIV │ r32 │ 88 │ 41 │ 41 ║
║ IDIV │ m8 │ 42 │ 17 │ 17 ║
║ IDIV │ m16 │ 57 │ 25 │ 25 ║
║ IDIV │ m32 │ 89 │ 41 │ 41 ║
╚═════════════╧══════════╧═════╧═════════╧═══════════════════════╝
同じことがIntel Pentiumにも当てはまります
╔═════════════╤══════════╤══════════════╗
║ Instruction │ Operands │ Clock cycles ║
╠═════════════╪══════════╪══════════════╣
║ DIV │ r8/m8 │ 17 ║
║ DIV │ r16/m16 │ 25 ║
║ DIV │ r32/m32 │ 41 ║
║ IDIV │ r8/m8 │ 22 ║
║ IDIV │ r16/m16 │ 30 ║
║ IDIV │ r32/m32 │ 46 ║
╚═════════════╧══════════╧══════════════╝
もちろん、それらは非常に古いものです。より多くのトランジスタを備えた新しいアーキテクチャはギャップを埋めるかもしれませんが、基本的なことは当てはまります。一般に、より多くのマクロ演算、より多くのロジック、符号付き除算を行うためのより多くのレイテンシが必要です。
要するに、事実の前に気にしないでください。しかし、後で気にします。
パフォーマンスが必要な場合は、コンパイラのパフォーマンス最適化を使用する必要がありますが、これは常識に反する場合があります。覚えておくべきことの1つは、異なるコンパイラは異なる方法でコードをコンパイルでき、それら自体が異なる種類の最適化を持っているということです。 g++
コンパイラについて話しているときに、-Ofast
、または少なくとも-O3
フラグを使用して最適化レベルを最大化することについて話している場合、私の経験ではlong
コードに入力すると、どのunsigned
型、またはint
よりもパフォーマンスが向上します。
これは私自身の経験によるものであり、実際のコードを手に入れて最適化してコンパイルし、実際に実行するタイプを選択できるようになったら、最初に完全なプログラムを作成し、そのようなことに注意することをお勧めしますベスト。これは、パフォーマンスのためのコード最適化に関する非常に一般的な提案でもあります。最初にすばやく記述し、最適化でコンパイルしてみてください。また、さまざまなコンパイラを使用してプログラムをコンパイルし、最もパフォーマンスの高いマシンコードを出力するものを選択する必要があります。
最適化されたマルチスレッド線形代数計算プログラムは、10倍以上のパフォーマンスの違いを簡単に持つことができます最適化されたものと最適化されていないものがあります。これは重要です。
多くの場合、オプティマイザーの出力はロジックと矛盾します。たとえば、a[x]+=b
とa[x]=b
の違いがプログラムの実行時間をほぼ2倍に変更した場合がありました。いいえ、a[x]=b
は高速ではありませんでした。
以下に例を示します NVidia stating GPUのプログラミングの場合:
注:既に推奨されるベストプラクティスであるように、SMMで最高のスループットを得るためには、可能な限り符号なし演算よりも符号付き演算を優先する必要があります。 C言語標準では、符号なし数学のオーバーフロー動作により多くの制限を設けており、コンパイラの最適化の機会を制限しています。
IIRC、x86の署名付き/署名なしでは違いはありません。一方、Short/longは異なるストーリーです。RAMに移動する必要があるデータの量がlongの場合は大きい(他の理由には、拡張などのキャスト操作が含まれる場合があるため)短から長)。
符号付きおよび符号なし整数は常に単一クロック命令として動作し、同じ読み取り/書き込みパフォーマンスを持ちますが、 Dr Andrei Alexandresc unsignedが符号付きよりも優先されます。これは、符号ビットを無駄にせず、ROMの減少によるパフォーマンス向上をもたらす負の数をチェックする命令を少なくするため、同じビット数に2倍の数の数値を収めることができるためです。 Kabuki VM の私の経験では、超高性能 Script 実装を特徴としており、メモリを操作するときに実際に符号付きの数値が必要になることはめったにありません。符号付きおよび符号なしの数値を使用してポインター演算を何年も費やしてきましたが、符号ビットが不要な場合は符号付きの利点がありません。
符号付き2の補数整数を使用して負の2のべき乗の除算を実行できるため、ビットシフトを使用して2のべき乗の乗算と除算を実行する場合、符号付きが好ましい場合があります。最適化の手法については、いくつかの AndreiのYouTube動画 をご覧ください。 世界最速の整数から文字列への変換アルゴリズム についての私の記事でいくつかの良い情報を見つけることもできます。
従来、int
はターゲットハードウェアプラットフォームのネイティブ整数形式です。他の整数型では、パフォーマンスが低下する可能性があります。
編集:
最新のシステムでは状況が少し異なります。
int
は、互換性の理由から、64ビットシステムでは実際には32ビットである場合があります。これはWindowsシステムで起こると思います。
最近のコンパイラは、場合によっては短い型の計算を実行するときにint
を暗黙的に使用する場合があります。
符号なし整数は、両方をビットストリームとして格納および処理するという利点があります。つまり、符号なしの単なるデータであるため、ビットシフト演算で乗算、除算が容易になります(高速になります)。