どのオペコードがCPUレベルで高速ですか?[閉まっている]


19

すべてのプログラミング言語には、他よりも推奨されるオペコードのセットがあります。ここでそれらを速度の順にリストしようとしました。

  1. ビット単位
  2. 整数の加算/減算
  3. 整数の乗算/除算
  4. 比較
  5. 制御フロー
  6. フロートの加算/減算
  7. フロート乗算/除算

高性能コードが必要な場合は、C ++をアセンブリで手作業で最適化して、SIMD命令またはより効率的な制御フロー、データ型などを使用できます。したがって、データ型(int32 / float32 / float64)または使用される動作(*+&)、CPUレベルの性能に影響を与えます。

  1. CPUでの単一の乗算は加算よりも遅いですか?
  2. MCU理論では、オペコードの速度は実行にかかるCPUサイクルの数によって決定されることを学びます。では、乗算に4サイクルかかり、加算に2サイクルかかるということですか?
  3. 基本的な数学および制御フローのオペコードの速度特性は正確に何ですか?
  4. 2つのオペコードの実行に同じサイクル数が必要な場合、両方のパフォーマンスコードはパフォーマンスの向上/損失なしで互換的に使用できますか?
  5. x86 CPUのパフォーマンスに関して共有できるその他の技術的な詳細は歓迎します

17
これは時期尚早の最適化のように聞こえますが、コンパイラは入力した内容を出力しないので、本当に必要な場合以外はアセンブリを書きたくないことを忘れないでください。
ロイT.

3
フロートの乗算と除算はまったく別のものです。同じカテゴリに入れないでください。nビット数の場合、乗算はO(n)プロセスであり、除算はO(nlogn)プロセスです。これにより、最新のCPUでの乗算よりも除算が約5倍遅くなります。
サムホセバル

1
唯一の本当の答えは「プロファイル」です。
テトラッド

1
Royの答えを拡張すると、手作業で最適化されたアセンブリは、本当に例外的でない限り、ほとんど常に純損失になります。現代のCPUは非常に複雑な獣であり、優れた最適化コンパイラーは、完全に非自明で、手作業でコードを作成するのは簡単ではないコード変換を実行します。SSE / SIMDの場合でも、常にC / C ++の組み込み関数を常に使用し、コンパイラーに使用を最適化させます。生のアセンブリを使用すると、コンパイラの最適化が無効になり、大きな損失が発生します。
ショーンミドルディッチ

SIMDを使用するためにアセンブリを手動で最適化する必要はありません。SIMDは状況に応じて最適化するのに非常に役立ちますが、SSE2を使用するためのほぼ標準的な規則があります(少なくともGCCとMSVCで動作します)。リストに関する限り、最新のスーパーパイプラインマルチパイプラインプロセッサでは、データの依存性とレジスタのプレッシャーにより、生の整数や浮動小数点のパフォーマンスよりも多くの問題が発生します。データの局所性についても同じことが言えます。ところで、整数の割り算は、現代のx86上の乗算と同じである
OrgnlDave

回答:


26

Agner Fogの最適化ガイドは優れています。彼は、ガイド、命令のタイミングの表、および最近のすべてのx86 CPU設計のマイクロアーキテクチャに関するドキュメントを持っています(Intel Pentiumま​​で遡ります)。/programming//tags/x86/infoからリンクされている他のリソースも参照してください。

楽しみのために、いくつかの質問に答えます(最近のIntel CPUの数値)。opsの選択は、コードを最適化する際の主要な要因ではありません(除算を回避できない場合を除く)。

CPUでの単一の乗算は加算よりも遅いですか?

はい(2の累乗でない限り)。(Intelのクロックスループットごとに1つだけで、レイテンシは3〜4倍です)。

基本的な数学および制御フローのオペコードの速度特性は正確に何ですか?

正確に知りたい場合は、Agner Fogの指示表とマイクロアーキテクチャガイドを参照してください。条件付きジャンプには注意してください。無条件ジャンプ(関数呼び出しなど)には多少のオーバーヘッドがありますが、それほどではありません。

2つのオペコードの実行に同じサイクル数が必要な場合、両方のパフォーマンスコードはパフォーマンスの向上/損失なしで互換的に使用できますか?

いいえ、彼らは他と同じ実行ポートをめぐって競争するかもしれませんし、競争しないかもしれません。CPUが並行して処理できる依存関係チェーンに依存します。(実際には、通常、有益な決定を下す必要はありません。IntelCPUの異なるポートで実行されるベクトルシフトまたはベクトルシャッフルを使用できる場合があります。しかし、レジスタ全体のバイト単位のシフト(PSLLDQなど)シャッフルユニットで実行されます。)

x86 CPUのパフォーマンスに関して共有できるその他の技術的な詳細は歓迎します

Agner Fogのマイクロアーキテクチャドキュメントでは、IntelおよびAMD CPUのパイプラインについて、ループが反復ごとに必要なサイクル数を正確に計算するのに十分な詳細と、ボトルネックがuopスループット、依存関係チェーン、または1つの実行ポートの競合であるかを説明しています。同様に、StackOverflowの上の私の答えのいくつかを参照してください、この1またはこのいずれか

また、 CPU設計が好きな場合は、http://www.realworldtech.com/haswell-cpu/(および以前の設計でも同様)を読んでください。

以下は、私のベストゲストに基づいて、Haswell CPU用にソートされたリストです。ただし、これは実際には、asmループのチューニング以外のことについて考える便利な方法ではありません。通常、キャッシュ/分岐予測効果が支配的であるため、適切なパターンを持つようにコードを記述してください。数値は非常に手作業で発生し、スループットが問題にならない場合でも高いレイテンシを考慮したり、他の事柄が並行して発生するためにパイプを詰まらせるuopをさらに生成したりします。特に キャッシュ/ブランチ番号は非常に構成されています。ループキャリーの依存関係ではレイテンシが重要であり、各反復が独立している場合はスループットが重要です。

TL:DRこれらの数値は、レイテンシ、実行ポートのボトルネック、フロントエンドスループット(またはブランチミスなどのストール)のトレードオフに関する限り、「典型的な」ユースケースについて私が描いているものに基づいて構成されています。 )。 深刻なパフォーマンス分析には、これらの数値を使用しないでください

  • 0.5〜1ビット単位/整数加算/減算/
    シフトと回転(コンパイル時のconstカウント)/
    これらすべてのベクトルバージョン(1サイクルあたり1〜4スループット、1サイクルレイテンシ)
  • 1つのベクトル、最小、最大、比較-等しい、比較-より大きい(マスクを作成するため)
  • 1.5ベクトルシャッフル。Haswell以降のシャッフルポートは1つしかありません。必要な場合はシャッフルを頻繁に行う必要があるので、シャッフルの使用を減らすことを検討するために、少し高めに重み付けしています。特に無料ではありません。メモリからpshufbコントロールマスクが必要な場合。
  • 1.5ロード/ストア(L1キャッシュヒット。待ち時間よりスループットが向上)
  • 1.75整数乗算(Intelでは1c tputあたり3cレイテンシ/ 1、AMDでは4c lat、2c tputあたり1つのみ)。LEAやADD / SUB / shiftを使用すると、小さな定数はさらに安価になります。しかし、もちろん、コンパイル時の定数は常に適切であり、多くの場合、他のものに最適化できます。(そして、ループ内の乗算は、コンパイラによってのtmp += 7代わりにループ内の強度が低下することがよくありますtmp = i*7
  • 1.75いくつかの256bベクトルシャッフル(AVXベクトルの128bレーン間でデータを移動できるinsnの余分な遅延)。(または、車線の交差シャッフルがより多くのuopを必要とするRyzenでは3から7)
  • 2 fp add / sub(および同じベクトルバージョン)(サイクルスループットあたり1または2、スループット3〜5サイクル)。遅延のボトルネックがある場合、たとえばsum変数が1つしかない配列を合計すると、遅くなる可能性があります。(ユースケースに応じて、この重みとfp mulを最小1または最大5に重み付けできます)。
  • 2ベクトルfp mulまたはFMA。(FMAサポートを有効にしてコンパイルすると、x * y + zはmulまたはaddと同じくらい安くなります)。
  • 2汎用レジスターをベクター要素に挿入/抽出します(_mm_insert_epi8など)
  • 2.25 vector int mul(16ビット要素または8 * 8-> 16ビットを行うpmaddubsw)。Skylakeで安く、スカラーマルよりもスループットが良い
  • 可変カウントによる2.25シフト/ローテート(2cレイテンシ、Intelでは2cスループットごとに1つ、AMDまたはBMI2では高速)
  • 2.5分岐なしの比較(y = x ? a : b、またはy = x >= 0)(test / setccまたはcmov
  • 3 int-> float変換
  • 3つの完全に予測された制御フロー(予測された分岐、呼び出し、戻り)。
  • 4ベクトルint mul(32ビット要素)(2 uops、Haswellで10cレイテンシ)
  • 4整数除算または%コンパイル時定数(2のべき乗以外)。
  • 7つのベクトル水平操作(たとえばPHADD、ベクトル内に値を追加)
  • 11(ベクター)FP除算(10〜13cのレイテンシ、7cスループットあたり1つまたはそれ以下)。(めったに使用しない場合は安くなる可能性がありますがスループットはFPマルチより6〜40倍劣ります)
  • 13?制御フロー(不十分に予測された分岐、おそらく75%予測可能)
  • 13 int除算(はい、FP除算よりも遅く、ベクトル化できません)(コンパイラは、mul / shift / addを使用して定数で除算し、魔法の定数を使用して除算します。div/ modの2の累乗は非常に安価です。)
  • 16(ベクター)FP sqrt
  • 25?ロード(L3キャッシュヒット)。(キャッシュミスストアはロードよりも安価です。)
  • 50?FPトリガー/ exp /ログ。多くのexp / logが必要で、完全な正確さを必要としない場合、より短い多項式やテーブルで正確さをスピードと引き換えにできます。 SIMDベクトル化することもできます。
  • 50-80? 常に予測ミスの分岐、コストは15〜20サイクル
  • 200-400?ロード/ストア(キャッシュミス)
  • 3000 ??? ファイルからページを読み込む(OSディスクキャッシュヒット)(ここで数値を作成)
  • 20000 ??? ディスク読み取りページ(OSディスクキャッシュミス、高速SSD)(完全に構成された数)

推測に基づいてこれを完全に作り上げました。何かが間違っているように見える場合、それは私が別のユースケースを考えていたか、または編集エラーのためです。

AMD CPUの相対的なコストは、shift-countが可変の場合に高速の整数シフターを使用することを除いて同様です。AMD BulldozerファミリのCPUは、さまざまな理由で、ほとんどのコードでもちろん低速です。(Ryzenは多くのことをかなり得意としています)。

物事を一次元のコストまで煮詰めることは本当に不可能であることに留意してください。キャッシュミスと分岐の予測ミス以外に、コードブロックのボトルネックは、レイテンシ、合計uopスループット(フロントエンド)、または特定のポート(実行ポート)のスループットになります。

FP除算のような「遅い」操作は、周囲のコードが他の作業でCPUをビジーに保つ場合、非常に安価になる可能性があります。(ベクトルFP divまたはsqrtはそれぞれ1 uopです。遅延とスループットが悪いだけです。分割ユニットのみをブロックします。実行ユニット全体はブロックしません。整数divは数uopです。) 〜20 mulごとに追加し、CPUが実行する他の作業(独立ループの繰り返しなど)がある場合、FP divの「コスト」はFP mulとほぼ同じになる可能性があります。これはおそらく、実行しているすべての場合のスループットが低いが、合計uopsが低いため、他のコードと非常によく混ざり合っている(遅延が要因ではない場合)の最良の例です。

整数除算は、周囲のコードとそれほど友好的ではないことに注意してください。Haswellでは、8〜11cのスループットごとに1回、22〜29cのレイテンシで9 uopです。(Skylakeでも64ビットの除算ははるかに遅くなります。)レイテンシとスループットの数値はFP divにいくらか似ていますが、FP divは1つのuopにすぎません。

insnsの短いシーケンスをスループット、レイテンシ、および合計uopについて分析する例については、SOの回答の一部を参照してください。

他の人がこの種の分析を含むSO回答を書く場合、IDK。私は自分自身を見つけるのがはるかに簡単になりました。なぜなら、私はこの詳細に頻繁にアクセスすることを知っているからです。


4の「予測されたブランチ」は理にかなっています。20〜25の「予測されたブランチ」は本当にどうあるべきでしょうか。(! -大きなテーブルのおかげで、私は13の周りに列挙され誤予測された分岐は、()はるかに高価よりもあったが、私はこのページでよ、なぜそれが近い真実に何かを学ぶために、正確だと思っていました)
マット

@Matt:これは編集エラーであり、「予測ミスされたブランチ」であると思われました。それを指摘してくれてありがとう。13は、常に予測が間違っているブランチではなく、予測が不完全なブランチであることに注意してください。手を振り直し、編集しました。:P
ピーターコーデス

16

問題のCPUに依存しますが、最新のCPUの場合、リストは次のようになります。

  1. ビット単位、加算、減算、比較、乗算
  2. 分割
  3. 制御フロー(回答3を参照)

CPUによっては、64ビットデータ型を操作するのにかなりの犠牲が生じる場合があります。

あなたの質問:

  1. 最新のCPUではまったく、またはあまり認識されていません。CPUに依存します。
  2. その情報は20年から30年古いようなものです(学校は吸う、あなたは今証拠を持っています)、現代のCPUはクロックごとに可変数の命令を処理します。
  3. 分割は他の部分よりも少し遅く、分岐予測が正しい場合は制御フローが非常に速く、間違っている場合は非常に遅くなります(20サイクルなど、CPUに依存します)。その結果、多くのコードは主に制御フローによって制限されます。if算術で合理的にできることでやらないでください。
  4. 命令のサイクル数に固定数はありませんが、2つの異なる命令が同等に実行される場合があり、別のコンテキストに配置されたり、そうでない場合があります。
  5. 制御フローに加えて、もう1つの大きな無駄はキャッシュミスです。キャッシュにないデータを読み取ろうとすると、CPUはメモリからデータがフェッチされるのを待たなければなりません。一般に、データをすべての場所から取り出すのではなく、データを隣り合わせに同時に処理するようにしてください。

最後に、ゲームを作成している場合は、これらすべてについてあまり心配する必要はありません。CPUサイクルを切り刻むよりも良いゲームを作成することに集中してください。


また、FPUは非常に高速です。特にIntelでは、固定小数点は確定的な結果が必要な場合にのみ必要です。
ジョナサンディキンソン

2
最後の部分にもっと重点を置いて、良いゲームを作りましょう。コードを明確にすることが役立ちます。これが、パフォーマンスの問題を実際に測定するときにのみ適用される理由です。必要に応じて、これらのifをより良いものに変更することは常に簡単です。一方で、5はよりトリッキーです。これは、通常、アーキテクチャを変更することを意味するため、実際に最初に考えたい場合であることは間違いありません。
ルアーン14

3

x64_64で100万回ループする整数演算に関するテストを行い、以下のような簡単な結論に達しました。

追加--- 116マイクロ秒

サブ---- 116マイクロ秒

mul ---- 1036マイクロ秒

div ---- 13037マイクロ秒

上記のデータは、ループによって引き起こされるオーバーヘッドをすでに削減しています。


2

Intelプロセッサのマニュアルは、Webサイトから無料でダウンロードできます。彼らはかなり大きいですが、技術的にあなたの質問に答えることができます。特に最適化マニュアルはあなたが求めているものですが、取扱説明書にはチップごとに異なるため、simd命令のほとんどの主要なCPUラインのタイミングとレイテンシもあります。

一般に、フルブランチとポインターチェイス(リンクリストトラバーラル、仮想関数の呼び出し)をパフォーマンスキラーの上位と見なしますが、x86 / x64 cpusは他のアーキテクチャと比較して両方とも非常に優れています。高性能のコードを書いている場合、別のプラットフォームに移植すると、どれだけの問題が発生するかがわかります。


+1、依存負荷(ポインター追跡)は大したことです。キャッシュミスは、将来のロードが開始することさえブロックします。一度にメインメモリから多くのロードを一度に実行すると、1回の操作で完全に完了するために前の操作が必要になるよりもはるかに優れた帯域幅が得られます。
ピーターコーデス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.