回答:
int(任意の種類)とfloatの混合による大きなペナルティは、これらが異なるレジスタセットにあるためです。あるレジスタセットから別のレジスタセットに移動するには、値をメモリに書き込んでから読み戻す必要があります。これにより、load-hit-storeストールが発生します。
異なるサイズまたはintの符号付きの間を移動すると、すべてが同じレジスタセットに保持されるため、大きなペナルティを回避できます。符号拡張などにより小さなペナルティーがありますが、これらはロードヒットストアよりもはるかに小さくなります。
Xbox 360とPS3に関する情報は、ほとんどの低レベルの詳細と同様に、ライセンスされた開発者専用の壁の背後にあると思われます。ただし、同等のx86プログラムを作成し、それを逆アセンブルして、一般的なアイデアを得ることができます。
まず、符号なしの拡張コストを見てみましょう。
unsigned char x = 1;
unsigned int y = 1;
unsigned int z;
z = x;
z = y;
関連部分は次のように分解されます(GCC 4.4.5を使用):
z = x;
27: 0f b6 45 ff movzbl -0x1(%ebp),%eax
2b: 89 45 f4 mov %eax,-0xc(%ebp)
z = y;
2e: 8b 45 f8 mov -0x8(%ebp),%eax
31: 89 45 f4 mov %eax,-0xc(%ebp)
基本的に同じです-ある場合にはバイトを移動し、別の場合には単語を移動します。次:
signed char x = 1;
signed int y = 1;
signed int z;
z = x;
z = y;
に変わる:
z = x;
11: 0f be 45 ff movsbl -0x1(%ebp),%eax
15: 89 45 f4 mov %eax,-0xc(%ebp)
z = y;
18: 8b 45 f8 mov -0x8(%ebp),%eax
1b: 89 45 f4 mov %eax,-0xc(%ebp)
そのため、符号拡張のコストは、サブ命令レベルではmovsbl
なく、どのようなコストでもmovzbl
あります。これは、最新のプロセッサが機能する方法のため、最新のプロセッサで定量化することは基本的に不可能です。メモリ速度からキャッシュ、事前にパイプラインにあったものまで、その他すべてがランタイムを支配します。
これらのテストを作成するのに10分ほどかかりましたが、実際のパフォーマンスバグを簡単に見つけることができました。また、コンパイラの最適化のレベルをオンにすると、そのような簡単なタスクではコードが認識できなくなります。
これはスタックオーバーフローではないので、ここで誰もマイクロ最適化が重要でないと主張しないことを願っています。ゲームは多くの場合、非常に大きく非常に数値の大きいデータで動作するため、分岐、キャスト、スケジューリング、構造のアライメントなどに注意を払うと、非常に重要な改善が得られます。PPCコードの最適化に多くの時間を費やした人は、おそらくロードヒットストアに関する少なくとも1つの恐怖物語を持っているでしょう。しかし、この場合、それは本当に重要ではありません。整数型のストレージサイズは、整列されてレジスタに収まる限り、パフォーマンスに影響しません。
符号付き整数演算は、ほぼすべてのアーキテクチャでより高価になる可能性があります。たとえば、定数による除算は、符号なしの方が高速です。たとえば、
unsigned foo(unsigned a) { return a / 1024U; }
以下に最適化されます:
unsigned foo(unsigned a) { return a >> 10; }
しかし...
int foo(int a) { return a / 1024; }
最適化:
int foo(int a) {
return (a + 1023 * (a < 0)) >> 10;
}
または分岐が安価なシステムでは、
int foo(int a) {
if (a >= 0) return a >> 10;
else return (a + 1023) >> 10;
}
モジュロについても同様です。これは、2のべき乗以外にも当てはまります(ただし、例はより複雑です)。アーキテクチャにハードウェア分割がない場合(たとえば、ほとんどのARM)、非定数の符号なし分割も高速です。
一般に、負の数は結果として得られないことをコンパイラーに伝えることは、式、特にループ終了やその他の条件に使用される式の最適化に役立ちます。
異なるサイズのintに関しては、はい、わずかな影響がありますが、メモリを移動することと比べて、それを比較検討する必要があります。最近では、サイズの拡張によって失われるよりも少ないメモリにアクセスする方がより多くの利益を得られるでしょう。その時点で、マイクロ最適化に非常に興味があります。
符号付きまたは符号なしintを使用した操作は、現在のプロセッサ(x86_64、x86、powerpc、arm)で同じコストがかかります。32ビットプロセッサでは、u32、u16、u8、s32、s16、s8は同じでなければなりません。アラインメントが悪いとペナルティを受ける可能性があります。
ただし、intをfloatに変換するか、floatをintに変換するのはコストのかかる操作です。最適化された実装(SSE2、Neonなど)を簡単に見つけることができます。
最も重要な点は、おそらくメモリアクセスです。データがL1 / L2キャッシュに収まらない場合、変換よりも多くのサイクルを失います。
Jon Purdyは、上記のように(私はコメントできませんが)署名できないとオーバーフローしないため、遅くなる可能性があると言います。私は同意しません、符号なし算術は、単語のビット数に2を法とする単純なモラー演算です。原則として、署名された操作はオーバーフローする可能性がありますが、通常は無効になっています。
時々、2つ以上のデータ項目をintにパックし、命令ごとに複数の操作を取得する(ポケット算術)など、賢い(しかし非常に読みやすいものではない)ことができます。しかし、あなたはあなたが何をしているのか理解する必要があります。もちろん、MMXではこれを自然に行うことができます。ただし、サポートされる最大のハードウェアワードサイズを使用し、データを手動でパックすると、実装が最速になる場合があります。
データのアライメントに注意してください。ほとんどのHW実装では、非整列のロードとストアが遅くなります。自然なアライメントとは、たとえば4バイトのワードの場合、アドレスは4の倍数であり、8バイトのワードアドレスは8バイトの倍数であることを意味します。これはSSEに引き継がれます(128ビットは16バイトのアライメントを優先します)。AVXはまもなくこれらの「ベクトル」レジスタサイズを256ビットに拡張し、次に512ビットに拡張します。また、アライメントされたロード/ストアは、アライメントされていないロード/ストアよりも高速になります。ハードウェアオタクの場合、非境界整列メモリ操作は、キャッシュラインやページ境界などにも及ぶ可能性があります。そのため、ハードウェアは注意する必要があります。
符号付きオーバーフローはCでは未定義であるため、ループインデックスに符号付き整数を使用する方がわずかに優れています。そのため、コンパイラはそのようなループのコーナーケースが少ないと想定します。これは、gccの「-fstrict-overflow」(デフォルトで有効)によって制御されており、アセンブリの出力を読み取らない限り、この効果はおそらく気づきにくいでしょう。
それを超えて、x86はメモリオペランドを使用できるため、タイプを混在させない方がうまく機能します。タイプ(符号またはゼロ拡張)を変換する必要がある場合、明示的なロードとレジスタの使用を意味します。
ローカル変数にはintを使用すると、デフォルトでこれが発生します。
セリオンが指摘するように、intとfloatの間の変換のオーバーヘッドは、レジスタ間の値のコピーと変換に大きく関係しています。符号なし整数自体のオーバーヘッドは、保証されたラップアラウンド動作に起因するため、コンパイルされたコードで一定量のオーバーフローチェックが必要になります。
基本的に、符号付き整数と符号なし整数の間の変換にオーバーヘッドはありません。異なるサイズの整数は、プラットフォームに応じて(無限に)高速または低速にアクセスできます。一般的に、プラットフォームのワードサイズに最も近い整数のサイズはアクセスが最も速くなりますが、全体的なパフォーマンスの違いは他の多くの要因、特にキャッシュサイズに依存します:uint64_t
必要なときに使用する場合uint32_t
、一度にキャッシュに収まるデータが少なくなり、負荷のオーバーヘッドが発生する可能性があります。
ただし、これについて考えることは少し過剰です。データに適した型を使用する場合、物事は完全にうまく機能するはずであり、アーキテクチャに基づいて型を選択することで得られるパワーの量はとにかく無視できます。