最も遅いx86命令

誰もがx86アセンブラーを知っており、愛しています。 その命令のほとんどは、単位またはナノ秒の小数単位で最新のプロセッサによって実行されます。 マイクロコードの長いシーケンスにデコードされるか、メモリへのアクセスを待機する一部の操作は、はるかに長い時間(最大数百ナノ秒)を要する場合があります。 この投稿はチャンピオンに関するものです。 カットの下で4つの命令のヒットパレードですが、本文全体を読むのが面倒な人のために、特定の条件下でメインの悪役は[メモリ] ++であるとここで書きます。



画像



CPAPは、Agner Foghによる文書から取られています。AgnerFoghには、Intelからの2つの文書(最適化ガイドおよびアーキテクチャソフトウェア開発マニュアル)とともに、多くの有用で興味深いトピックが含まれています。



そもそも、マイクロ秒以内に実行することが期待されるコマンドがあります。 たとえば、IN、OUT、RSM(SMMからの戻り)。 VMEXITは近年劇的に加速しており、新しいプロセッサではマイクロ秒の何分の1かが続きます。 定義により可能な限り実行されるMWAITがあります。 一般に、リング0には多数の「重い」命令があり、すべてマイクロコード(WRMSR、CPUID、制御レジスタの設定など)で構成されています。 以下に示す例は、リング3の特権で、つまり通常のプログラムで実行できます。 Cでのプログラミングも必要ありません。一部の一般的な言語の仮想マシンでは、これらの操作を含むコードを生成できます。 これらは特別なプロセッサコマンドではなく、通常の命令であり、場合によっては特別な条件になります。



これらは長時間実行されるため、当然のことながら、かなり「ホットな」コードで検出されない限り、正常なプロファイラーは通常の時間プロファイルでそれらを検出します。 そのような場合に排他的に応答する別個のパフォーマンスカウンター(PMUレジスタ)がまだあり、それらの助けを借りて、たとえ絶対的な時間があまりかからなくても(理由だけで)大規模なプログラムでこれらの操作を見つけることができます。 このための最も一般的なツールは、 VtuneとLinux perfです。 PCMを使用することもできますが、命令の場所は表示されません。



悪役数4。 ほぼ700クロックサイクルを実行できるx86コマンド(実際にはx87)はFYl2Xです。 第2オペランドを乗算した2進数の対数を計算します。 SSEには類似物が存在しないため、自然界にまだ存在しています。 特別なカウンターはありません。



悪役3番。 おそらく少し人工的な例ですが、頻繁に使用されます。 幸いなことに、主にドライバーで。

MFENCEチーム(またはそのサブセット-LFENCE、SFENCE。ところで、LFENCE + SFENCE!= MFENCE)。 MFENCEの前にメモリまたはPCIe書き込みを伴う長い操作、たとえば、非一時的な操作(MOVNTI、MOVNTPS、MASKMOVDQUなど)、またはオペランドが書き込みスルー/書き込み結合メモリ領域にある操作が実行された場合、「フェンス」自体ほぼ1マイクロ秒以上かかります。 この状況にはパフォーマンスカウンターがありますが、プロセッサコアにはありませんが、「アンコア」では、PCMを介して操作する方が簡単です。



悪役ナンバー2。 これは非常に単純なコードです。

 double fptest = 3000000000.0f;  // floatと同じ。
 // TSC1
 int inttest = 2 + fptest;
 // TSC2
時間= TSC2-TSC1;


時間とほぼ等しいと思いますか? (このコードがx87にコンパイルされるか、スカラーSSEにコンパイルされるかは関係ありません)。 この単一の命令は1〜2マイクロ秒で実行されます。 これはいわゆる非正規化操作であり、マイクロコードの長いシーケンスによって処理される特殊なケースです。 PMUレジスタ-FP_ASSIST.ALLというパフォーマンスカウンターを簡単に取得できます。 ところで、1つ(または数十個)の命令を実行するときにTSCの差を測定することは、ほとんど常に無意味であることは明らかです。 このケースは例外です。長いマイクロコードを測定します。



主な悪役。

静的な符号なしchar配列[128];
 for(int i = 0; i <64; i ++)if((int)(array + i)%64 == 63)break;
 lock =(unsigned int *)(array + i);
 for(i = 0; i <1024; i ++)*(lock)++;  //プライム
 // TSC1
    asm volatile(
     「xaddl%1、(%0)\ nをロック」
     ://出力なし
     : "r"(ロック)、 "r"(1));
    //またはWindowsでは、単にInterlockedIncrement(ロック);
 // TSC2
時間= TSC2-TSC1;


さて、ボーナス-ヒットパレードの他の参加者とは異なり、このコードは、他のすべてのカーネルも数千小節の間、スモークブレークのために停止します。

これは、Vtune、perf、PCMなどを使用しても捕捉されます。 カウンターLOCK_CYCLES.SPLIT_LOCK_UC_LOCK_DURATIONを使用します。 例はとてつもないように思えるかもしれませんが、この1年で、クライアントでこの問題に2回遭遇しました。 いずれかの場合、.netで書かれた巨大なプログラムを初期化するときに、LOCK_CYCLES.SPLIT_LOCK_UC_LOCK_DURATIONがスケールを外れました。 そのとき、私はそれを理解していませんでした。ランタイムまたはクライアントコードがミューテックスを見つけられなかったのですが、パフォーマンスが大幅に低下しました-別のカーネルで実行されている別の独立したプログラムが30倍遅くなりました。



誰かがさらに遅い命令を知っていますか? (REP MOVは提供しません)。



All Articles