重要な背景資料:Agner Fogのmicroarch pdfと、おそらくUlrich Drepperの「すべてのプログラマーがメモリについて知っておくべきこと」。の他のリンクも参照してくださいx86タグウィキ、特にIntelの最適化マニュアル、およびDavid Kanter によるHaswellマイクロアーキテクチャーの分析(図付き)。
とてもクールな割り当て。学生がのコードを最適化するように求められgcc -O0
た実際のコードでは関係のないたくさんのトリックを学んだ私が見たものよりもはるかに優れています。この場合、CPUパイプラインについて学び、それを使用して、盲目的な推測だけでなく、最適化解除の取り組みをガイドするよう求められます。 この1つの最も楽しい部分は、意図的な悪意ではなく、「悪魔のような無能」で悲観化を正当化することです。
割り当ての表現とコードの問題:
このコードのuarch固有のオプションは制限されています。配列を使用せず、コストの多くはexp
/ log
ライブラリ関数の呼び出しです。多かれ少なかれ命令レベルの並列処理を行う明白な方法はなく、ループで運ばれる依存チェーンは非常に短いです。
依存関係を変更するために式を再配置し、依存関係(ハザード)のみからILPを削減することで、速度を低下させようとする答えを見たいです。 私はそれを試みていません。
Intel SandybridgeファミリCPUは、多くのトランジスタと電力を使用して並列処理を検出し、従来のRISCインオーダーパイプラインに支障をきたす危険性(依存性)を回避する積極的なアウトオブオーダー設計です。通常、速度を低下させる従来の唯一の危険は、レイテンシによってスループットが制限される原因となるRAW「真の」依存関係です。
レジスタの名前変更により、レジスタのWARおよびWAWの問題はほとんど問題になりません。(以外popcnt
/lzcnt
/tzcnt
、持っている偽の依存関係にインテルのCPU上で目的地をそれの書き込み専用。すなわちWAWは、RAWハザード+書き込みとして扱われているにもかかわらず、)。メモリの順序付けでは、最新のCPUはストアキューを使用して、リタイアするまでキャッシュへのコミットを遅らせ、WARとWAWの危険も回避します。
Agnerの指示表とは異なり、mulssがHaswellで3サイクルしかかからないのはなぜですか?FPドット積ループでのレジスタ名の変更とFMAレイテンシの非表示について詳しく説明します。
"i7"ブランド名はNehalem(Core2の後継)で導入されました。一部のIntelマニュアルでは、Nehalemを意味すると思われる場合でも "Core i7"と記載されていますが、Sandybridgeおよびそれ以降のマイクロアーキテクチャでは "i7"ブランドを使用しています。 SnBは、P6ファミリーが新種のSnBファミリーに進化したときです。多くの点で、NehalemはPentium IIIとSandybridgeよりも共通点があります(たとえば、物理レジスタファイルを使用するように変更されたため、SnBではレジスタ読み取りストールとROB読み取りストールは発生しません。また、uopキャッシュと別の内部uop形式)。 「i7アーキテクチャ」という用語は役に立たない、SnBファミリをNehalemとグループ化しても、Core2とはグループ化しないのはほとんど意味がないためです。(ただし、Nehalemは、複数のコアを一緒に接続するための共有の包括的L3キャッシュアーキテクチャを導入しました。また、統合されたGPUも導入しました。したがって、チップレベルでのネーミングの方が理にかなっています。)
悪魔のような無能が正当化できる良いアイデアの要約
悪魔のような能力がない人でも、明らかに役に立たない作業や無限ループを追加する可能性は低く、C ++ / Boostクラスで混乱させることは、割り当ての範囲を超えています。
- 単一の共有
std::atomic<uint64_t>
ループカウンターを備えたマルチスレッドなので、適切な合計反復回数が発生します。Atomic uint64_tは、で特に悪い-m32 -march=i586
です。ボーナスポイントについては、位置がずれるように調整し、ページ境界を不均一な分割で横切ってください(4:4ではありません)。
- その他の非アトミック変数の誤った共有 ->メモリー順序の誤推論パイプラインのクリア、および追加のキャッシュミス。
-
FP変数で使用する代わりに、上位バイトと0x80をXORして符号ビットを反転し、ストア転送ストールを引き起こします。
- 各イテレーションを個別に計時し
RDTSC
ます。例:CPUID
/ RDTSC
またはシステムコールを作成する時間関数。直列化命令は本質的にパイプラインに対応していません。
- 定数による乗算を変更して、その逆数で除算します(「読みやすくするため」)。 divは遅く、完全にパイプライン化されていません。
- AVX(SIMD)でmultiply / sqrtをベクトル化します
vzeroupper
が、スカラーの数学ライブラリexp()
とlog()
関数の呼び出し前に使用できず、AVX <-> SSE遷移がストールします。
- RNG出力をリンクリストに格納するか、順不同でトラバースする配列に格納します。各反復の結果についても同じで、最後に合計します。
この回答にも含まれていますが、要約からは除外されています。パイプライン化されていないCPUでは速度が遅い、または悪質な能力がなくても正当化できないと思われる提案。たとえば、明らかに異なる/悪いasmを生成する多くのgimp-the-compilerアイデア。
マルチスレッドがひどい
たぶん、OpenMPを使用して、非常に少ない反復でマルチスレッドループを作成し、速度の向上よりもはるかに多くのオーバーヘッドをかけます。ただし、モンテカルロコードには、実際に速度を上げるのに十分な並列処理があります。各反復を遅くすることに成功した場合。(各スレッドpayoff_sum
は、最後に追加された部分を計算します)。 #omp parallel
そのループではおそらく最適化であり、悲観化ではありません。
マルチスレッドですが、両方のスレッドが同じループカウンターを共有するように強制しatomic
ます(増分の合計数が正しいため)。これは悪魔のように論理的です。つまり、static
変数をループカウンターとして使用します。この正当化は、使用のatomic
ループカウンタのため、そして作成し、実際のキャッシュライン行き来し続け(限りのスレッドがハイパースレッディングと同じ物理コア上で実行されていないとして、ではないかもしれませんように遅いです)。とにかく、これはの競合しない場合よりもはるかに遅くなりますlock inc
。また、32ビットシステムでlock cmpxchg8b
競合するものをアトミックにインクリメントするuint64_t
には、ハードウェアにアトミックなアービトレーションを行わせるのではなく、ループで再試行する必要がありますinc
。
また、偽の共有を作成します。この場合、複数のスレッドがプライベートデータ(RNG状態など)を同じキャッシュラインの異なるバイトに保持します。 (調べるためのパフォーマンスカウンターを含む、それに関するIntelチュートリアル)。 これにマイクロアーキテクチャ固有の側面があります:インテルのCPUは、メモリ誤発注が上推測ではない起こって、そしてありますメモリ次機械クリアPERFイベントは、少なくともP4上で、これを検出します。ペナルティはHaswellではそれほど大きくない可能性があります。そのリンクが指摘しているように、lock
ed命令はこれが起こると想定し、誤解を回避します。通常のロードでは、ロードが実行されてからプログラム順にリタイアするまでの間に、他のコアがキャッシュラインを無効にしないことが推測されます(を使用しない限りpause
)。lock
edの指示なしの真の共有は通常バグです。非アトミック共有ループカウンターをアトミックケースと比較すると興味深いでしょう。実際に悲観的にするには、共有アトミックループカウンターを保持し、他の変数の同じまたは異なるキャッシュラインで誤った共有を引き起こします。
ランダムなuarch固有のアイデア:
予測できないブランチを導入できる場合は、コードが大幅に悲観化されます。最近のx86 CPUは非常に長いパイプラインを持っているので、誤予測のコストは最大15サイクルです(uopキャッシュから実行する場合)。
依存チェーン:
これは割り当ての意図された部分の1つだったと思います。
複数の短い依存関係チェーンではなく、1つの長い依存関係チェーンを持つ操作の順序を選択して、命令レベルの並列処理を利用するCPUの機能を無効にします。-ffast-math
(以下で説明するように)結果を変更する可能性があるため、を使用しない限り、コンパイラーはFP計算の演算の順序を変更できません。
これを本当に効果的にするには、ループで運ばれる依存関係チェーンの長さを増やします。ただし、明白なものはありません。記述されているループには、ループを運ぶ依存関係のチェーンが非常に短く、FPの追加だけです。(3サイクル)。複数のイテレーションpayoff_sum +=
は、前のイテレーションの終わりのかなり前に開始できるため、一度に計算を実行できます。(log()
そしてexp
、多くの指示を受け取りますが、並列性を見つけるためのHaswellの順不同ウィンドウよりも多くはありません:ROBサイズ= 192融合ドメインuops、およびスケジューラサイズ= 60融合ドメインuops。現在のイテレーションの実行が次のイテレーションからの命令を発行する余地を作るのに十分なほど進行するとすぐに、古い命令が実行ユニットを離れると、入力の準備ができているその部分(つまり、独立した/分離したdepチェーン)が実行を開始できます。無料(たとえば、スループットではなく待ち時間でボトルネックになっているため)。
RNG状態は、ほぼ確実に、を超えるループキャリー依存チェーンになりますaddps
。
より遅い/より多くのFP演算を使用します(特に除算を増やします):
0.5を掛けるのではなく、2.0で割ります。FP乗算は、Intelデザインではパイプライン化されており、Haswell以降では0.5cごとに1つのスループットがあります。 FP divsd
/ divpd
は部分的にパイプライン化されています。(Skylakeのdivpd xmm
レイテンシは13〜14cであり、Nehalem(7〜22c)ではまったくパイプライン化されていないため、の4cあたりのスループットは1 です。)
do { ...; euclid_sq = x*x + y*y; } while (euclid_sq >= 1.0);
明確にそうはっきりそれが適切になり、距離のためにテストしているsqrt()
こと。:P(sqrt
よりもさらに遅いdiv
)。
@Paul Claytonが示唆しているように、連想/分散等価物を使用-ffast-math
して式を書き換えると、より多くの作業が発生する可能性があります(コンパイラーを再最適化するために使用しない限り)。 (exp(T*(r-0.5*v*v))
になる可能性がありexp(T*r - T*v*v/2.0)
ます。実数の計算は連想的ですが、オーバーフロー/ NaNを考慮しなくても、浮動小数点の計算はにはなりません(これが-ffast-math
デフォルトでオンになっていない理由です)。非常に毛深いネストされた提案については、ポールのコメントを参照してくださいpow()
。
計算を非常に小さな数値にスケールダウンできる場合、FP数学演算は、2つの通常の数値に対する演算が非正規化を生成するときに、マイクロコードにトラップするために約120サイクル余分にかかります。正確な数と詳細については、Agner Fogのmicroarch pdfを参照してください。乗算が多数あるため、これはありそうもないので、スケール係数は2乗され、0.0までアンダーフローします。必要なスケーリングを無能なもの(悪魔的なものも含む)で正当化する方法はありません。意図的な悪意だけです。
組み込み関数を使用できる場合(<immintrin.h>
)
movnti
キャッシュからデータを削除するために使用します。悪魔のような:それは新しく、順序が弱いので、CPUがより速く実行できるようになるはずですよね?または、誰かが正確にこれを行う危険にさらされていた場合のリンクされた質問を参照してください(一部の場所のみがホットだった分散した書き込みの場合)。 clflush
悪意がなければおそらく不可能です。
FP数学演算の間に整数シャッフルを使用して、バイパス遅延を引き起こします。
SSEとAVX命令を適切に使用せずに混在さvzeroupper
せると、 Skylake以前では大きなストールが発生します( Skylakeではペナルティが異なります)。それがなくても、ベクトル化はスカラーよりも悪くなる可能性があります(256bベクトルで一度に4つのモンテカルロ反復のadd / sub / mul / div / sqrt操作を実行することによって保存されるよりも、ベクトルへ/からデータをシャッフルするのに多くのサイクルが費やされます) 。add / sub / mul実行ユニットは完全にパイプライン化され、全幅ですが、256bベクトルのdivとsqrtは128bベクトル(またはスカラー)ほど高速ではないため、スピードアップは劇的ではありませんdouble
。
exp()
そしてlog()
一部が戻っスカラへのベクトル要素を抽出し、個別にライブラリ関数を呼び出し、その後、戻っベクトルに結果をシャッフル必要になりそうという、ハードウェアをサポートしていません。libmは通常、SSE2のみを使用するようにコンパイルされているため、スカラー数学命令のレガシーSSEエンコーディングを使用します。コードが256bベクトルを使用exp
し、vzeroupper
最初に実行せずに呼び出す場合、停止します。戻った後、vmovsd
次のベクトル要素をargとして設定するようなAVX-128命令exp
もストールします。そしてexp()
、それはSSE命令を実行したときに再び失速します。 これはまさにこの質問で起こったことであり、10倍のスローダウンを引き起こしています。 (@ZBosonに感謝します)。
このコードについては、Nathan KurzがIntelの数学ライブラリとglibcを比較した実験も参照してください。今後のglibcには、ベクトル化された実装などが含まれる予定です。exp()
IvB以前、またはespを対象とする場合。Nehalem、gccに16ビットまたは8ビットの操作とそれに続く32ビットまたは64ビットの操作を伴う部分レジスターのストールを引き起こすようにしてください。ほとんどの場合、gccはmovzx
8ビットまたは16ビットの操作の後に使用しますが、ここではgccが変更ah
してから読み取る場合を示します。ax
(インライン)asm:
(インライン)asmを使用すると、uopキャッシュを壊す可能性があります。3つの6uopキャッシュラインに収まらない32Bコードのチャンクは、uopキャッシュからデコーダーへの切り替えを強制します。内側のループ内のブランチターゲットで、数個の長いs の代わりにALIGN
多くのシングルバイトnop
s を使用する能力のnop
ない人がうまくいくかもしれません。または、配置パディングをラベルの前ではなく、ラベルの後に置きます。:Pこれは、フロントエンドがボトルネックである場合にのみ問題となり、コードの残りの部分を悲観化することに成功した場合は問題になりません。
自己変更コードを使用して、パイプラインのクリアをトリガーします(別名、マシンnukes)。
即値が大きすぎて8ビットに収まらない16ビット命令からのLCPストールは、役に立ちそうにありません。SnB以降のuopキャッシュは、デコードのペナルティを1回だけ支払うことを意味します。Nehalem(最初のi7)では、28 uopループバッファーに収まらないループで動作する可能性があります。gccは-mtune=intel
、32ビット命令を使用できた場合でも、そのような命令を生成することがあります。
タイミングの一般的なイディオムはCPUID
(シリアル化する)thenRDTSC
です。CPUID
/ RDTSC
を使用して各反復の時間を個別にRDTSC
計り、前の命令で順序が変更されないようにします。これにより、処理速度が大幅に低下します。(実際には、時間を計るスマートな方法は、すべての反復を個別に計時して合計するのではなく、すべての反復を一緒に計時することです)。
多くのキャッシュミスとその他のメモリのスローダウンを引き起こす
union { double d; char a[8]; }
一部の変数にはa を使用します。 バイトの1つだけに狭いストア(または読み取り-変更-書き込み)を実行することにより、ストア転送ストールを引き起こします。(そのwiki記事は、ロード/ストアキューに関する他の多くのマイクロアーキテクチャーに関するものもカバーしています)。たとえば、別のxmmレジスタの定数を使用して上位バイトのみでXOR 0x80を使用することの符号を反転しdouble
ます、演算子の代わりにます。悪魔のように能力のない開発者は、FPが整数よりも遅いと聞いたことがあるかもしれません。(SSEレジスタのFP演算を対象とする非常に優れたコンパイラは、これをが、x87にとってこれがひどくない唯一の方法は、コンパイラが値を否定していることを認識し、次の加算を差し引く。)-
xorps
を使用しvolatile
てコンパイルし-O3
、使用しない場合に使用してstd::atomic
、コンパイラーに実際にすべての場所にストア/リロードを強制します。(ローカルではなく)グローバル変数も一部のストア/リロードを強制しますが、C ++メモリモデルの弱い順序付けでは、コンパイラーが常にメモリにスピル/リロードする必要はありません。
ローカル変数を大きな構造体のメンバーに置き換えて、メモリレイアウトを制御できるようにします。
構造体で配列を使用して、パディング(および乱数を格納して、その存在を正当化します)。
すべてがL1キャッシュの同じ「セット」内の別の行に入るように、メモリレイアウトを選択します。これは、8ウェイの連想のみです。つまり、各セットには8つの「ウェイ」があります。キャッシュラインは64Bです。
さらに良いのは、4096Bを正確に離して配置することです。これは、ロードが異なるページへのストアに誤った依存関係を持っているため、ページ内のオフセットが同じだからです。アグレッシブアウトオブオーダーCPUは、メモリの曖昧性解消を使用して、結果を変更せずにロードとストアを並べ替えることができる時期を特定します。Intelの実装には、ロードの早期開始を防ぐ誤検知があります。おそらく、それらはページオフセットより下のビットのみをチェックするため、TLBが仮想ページから物理ページに上位ビットを変換する前にチェックを開始できます。Agnerのガイドと同様に、Stephen Canonからの回答と、同じ質問に対する@Krazy Glewの回答の終わり近くのセクションも参照してください。(Andy Glewは、インテルのオリジナルのP6マイクロアーキテクチャーのアーキテクトの1人でした。)
__attribute__((packed))
変数を誤って整列させて、キャッシュラインまたはページ境界にまたがるようにするために使用します。(したがって、1つのロードにはdouble
2つのキャッシュラインからのデータが必要です)。キャッシュラインとページラインを交差する場合を除いて、インテルi7のuarchではロードのミスアライメントによるペナルティはありません。 キャッシュラインの分割には、まだ余分なサイクルが必要です。Skylakeは、ページ分割ロードのペナルティを100から5サイクルに劇的に減らします。(セクション2.1.3)。おそらく、2つのページウォークを並行して実行できることに関連しています。
のページ分割はatomic<uint64_t>
、最悪の場合、特にです。1ページで5バイト、他のページで3バイト、または4:4以外の場合。途中で分割することも、IIRCの一部の地域では16Bベクトルを使用したキャッシュライン分割の場合により効率的です。alignas(4096) struct __attribute((packed))
もちろん、スペースを節約するために、RNGの結果を格納するための配列を含め、すべてをに配置します。カウンターの前で、uint8_t
またはuint16_t
何かを使用して、ミスアライメントを達成します。
コンパイラーにインデックス付きアドレッシングモードを使用させることができれば、uop micro-fusionは無効になります。たぶん、#define
sを使用して単純なスカラー変数をに置き換えますmy_data[constant]
。
追加レベルの間接参照を導入できるため、ロード/ストアアドレスが早期に認識されない場合、さらに悲観的になる可能性があります。
不連続な順序で配列をトラバースする
そもそも配列を導入するための無能な正当化を考え出すことができると思います。これにより、乱数の生成と乱数の使用を分離できます。各反復の結果を配列に格納して、後で合計することもできます(より悪魔的な能力がない場合)。
「最大のランダム性」の場合、ランダム配列をループして新しい乱数を書き込むスレッドを作成できます。乱数を消費するスレッドは、乱数をロードするためのランダムなインデックスを生成する可能性があります。(ここにはいくつかの作業がありますが、マイクロアーキテクチャー的には、ロードアドレスが早期にわかるため、ロードされたデータが必要になる前に、起こりうるロードレイテンシを解決できます。)異なるコアにリーダーとライターがあると、メモリの順序付けにミスが発生します。 -スペキュレーションパイプラインがクリアされます(偽共有の場合について前述したとおり)。
最大の悲観化のために、4096バイトのストライド(つまり、512ダブル)で配列をループします。例えば
for (int i=0 ; i<512; i++)
for (int j=i ; j<UPPER_BOUND ; j+=512)
monte_carlo_step(rng_array[j]);
アクセスパターンがあるので、0、4096、8192、...、
8、4104、8200、...
16、4112、8208、...
これはdouble rng_array[MAX_ROWS][512]
、間違った順序(@JesperJuhlが示唆するように、内部ループの行内の列ではなく、行をループする)で2D配列にアクセスするために得られるものです。悪魔のような無能がそのような次元の2D配列を正当化できる場合、庭のさまざまな実世界の無能は、間違ったアクセスパターンでのループを簡単に正当化します。これは実際のコードで発生します。
配列がそれほど大きくない場合は、必要に応じてループ境界を調整して、同じ数ページを再利用するのではなく、多くの異なるページを使用します。ハードウェアのプリフェッチは、ページ間で(またはまったく)機能しません。プリフェッチャーは、各ページ内で1つのフォワードストリームと1つのバックワードストリームを追跡できます(ここで行われることです)が、プリフェッチャーは、メモリ帯域幅が非プリフェッチでまだ飽和していない場合にのみ動作します。
これにより、ページがhugepageにマージされない限り、多くのTLBミスが発生します(Linuxはmalloc
/のような匿名の(ファイルに依存しない)割り当てに対してこれを日和見的に行いnew
ますmmap(MAP_ANONYMOUS)
)。
結果のリストを格納する配列の代わりに、リンクリストを使用できます。次に、すべての反復でポインター追跡ロードが必要になります(次のロードのロードアドレスに対するRAWの真の依存性ハザード)。アロケータが悪いと、リストノードをメモリ内に分散させ、キャッシュを無効にすることができます。悪質な能力のないアロケータを使用すると、すべてのノードを独自のページの先頭に置くことができます。(たとえばmmap(MAP_ANONYMOUS)
、適切にサポートするためにページを分割したりオブジェクトサイズを追跡したりせずに、直接割り当てますfree
)。
これらは実際にはマイクロアーキテクチャ固有ではなく、パイプラインとはほとんど関係がありません(これらのほとんどは、パイプライン化されていないCPUの速度低下にもなります)。
やや話題外:コンパイラに悪いコードを生成させる/より多くの作業を行わせる:
C ++ 11 std::atomic<int>
およびstd::atomic<double>
最も悲観的なコードを使用します。MFENCEとlock
ed命令は、別のスレッドからの競合がない場合でも、かなり低速です。
-m32
x87コードはSSE2コードよりも悪いため、コードは遅くなります。スタックベースの32ビットの呼び出し規約は、より多くの命令を取り、スタック上のFP引数ものような関数に渡しますexp()
。 atomic<uint64_t>::operator++
onに-m32
はlock cmpxchg8B
ループが必要です(i586)。(それをループカウンターに使用してください![悪笑い])。
-march=i386
悲観的になります(@Jesperに感謝します)。FPとの比較fcom
は686より遅いですfcomi
。586より前のatomic
バージョンでは、アトミック64ビットストア(cmpxchg はもちろんのこと)が提供されていないため、すべての64ビットopはlibgcc関数呼び出しにコンパイルされます(実際にロックを使用するのではなく、おそらくi686用にコンパイルされます)。最後の段落のGodbolt Compiler Explorerリンクで試してください。
sizeof()が10または16のABI(位置合わせ用のパディング付き)で精度と速度をさらに上げるには、long double
/ sqrtl
/ expl
を使用long double
します。(IIRC、64ビットWindowsでは、8バイトにlong double
相当しdouble
ます。(とにかく、10バイト(80ビット)FPオペランドのロード/ストアは4/7 uopsであるfloat
か、または/ double
に対してそれぞれ1 uop しかかかりません。)x87を強制すると、 gcc 。fld m64/m32
fst
long double
-m64 -march=haswell -O3
atomic<uint64_t>
ループカウンターを使用long double
しない場合は、ループカウンターを含むすべてに使用します。
atomic<double>
コンパイルしますが、+=
(64ビットでも)などの読み取り-変更-書き込み操作はサポートされていません。 atomic<long double>
アトミックなロード/ストアのためだけにライブラリ関数を呼び出す必要があります。x86 ISAはアトミックな10バイトのロード/ストアを自然にサポートしていないため、おそらく非効率的であり、ロック(cmpxchg16b
)なしで考えることができる唯一の方法は64ビットモードを必要とします。
では-O0
、一時的な変数にパーツを割り当てることによって大きな式を分割すると、より多くのストア/リロードが発生します。volatile
実際のコードの実際のビルドが使用する最適化設定では、これが何であれ、これは問題になりません。
Cのエイリアシングルールでは、a char
があらゆるものをエイリアスできるため、a を介して格納するとchar*
、コンパイラーはバイトストアの前後にすべてを格納/再ロードします-O3
。(これは、たとえばの配列を操作するuint8_t
自動ベクトル化コードの問題です。)
uint16_t
おそらく16ビットのオペランドサイズ(潜在的なストール)や追加のmovzx
命令(安全)を使用して、ループカウンターを試して16ビットに切り捨てることを強制します。 符号付きオーバーフローは未定義の動作なので、64ビットポインターへのオフセットとして使用されている場合でも-fwrapv
、少なくともを使用しない限り-fno-strict-overflow
、符号付きループカウンターを反復ごとに再符号拡張する必要はありません。
整数との間の変換を強制float
します。および/またはdouble
<=> float
変換。命令には1つ以上のレイテンシがあり、スカラーint-> float(cvtsi2ss
)は、xmmレジスタの残りをゼロにしないように設計されています。(pxor
このため、gccは依存関係を壊すために追加を挿入します。)
CPUアフィニティを別のCPUに頻繁に設定します(@Egworが推奨)。悪魔のような推論:1つのコアが長時間スレッドを実行することによって過熱されないようにしたいですか?多分別のコアに交換すると、そのコアがより高いクロック速度にターボできるようになります。(実際には、これらは熱的に非常に接近しているため、マルチソケットシステムを除いて、これはほとんどあり得ません)。ここで、調整を間違えて、頻繁に実行します。OSの保存/スレッド状態の復元に費やされた時間に加えて、新しいコアにはコールドL2 / L1キャッシュ、uopキャッシュ、および分岐予測子があります。
不必要なシステムコールを頻繁に導入すると、それらが何であっても速度が低下する可能性があります。のようないくつかの重要ですが単純なものgettimeofday
は、カーネルモードに移行せずに、ユーザー空間で実装できます。(カーネルはのコードをエクスポートするため、Linux上のglibcはカーネルの助けを借りてこれを行いますvdso
)。
システムコールのオーバーヘッド(コンテキストスイッチ自体だけでなく、ユーザースペースに戻った後のキャッシュ/ TLBミスを含む)の詳細については、FlexSCペーパーに、現在の状況の優れたパフォーマンスカウンター分析とバッチシステムの提案があります。大量のマルチスレッドサーバープロセスからの呼び出し。
while(true){}