8

パフォーマンスが重要なアプリケーションを開発しています。GCCで、memset()への特定の呼び出しを、「rep stos QWORD PTR es:[rdi]、rax」のような繰り返しプレフィックスを持つ命令として変換したいと思います。サイズが既知で小さい場合、GCCはこれを自動的に行います。

ただし、GCCは、PLTを介したmemset()の呼び出しを通じて、ランダムな長さのmemset()の呼び出しをマップします。これにより、分岐予測キャッシュがコールドであるため、分岐の予測ミスが発生します。

GCCに(インラインアセンブリ以外で)私が望むことを強制する方法はありますか?プログラム全体でこの動作を望まないことに注意してください。特定のmemset()呼び出しに対してのみです。

関連するトピックでは、cmovcc命令がジョブを実行するときにGCCが分岐するのを防ぐハックにも興味があります(&&の代わりに&、+などを使用することについて知っています)。

助けてくれてありがとう。

@FrankH:

それは基本的に私がやったことです。これが私のコードです:

static finline void app_zero(void *dst, uint32_t size, uint32_t count)
{
    // Warning: we tell gcc to use 'dst' both as source and destination here.  
    // This does not cause problems because we don't reuse 'dst'.  
    #ifdef APP_ARCH_X86 
    #define STOS(X,Y) do { \  
        int c = (size/Y)*count; \  
        __asm__ __volatile__("cld; xor %%eax, %%eax; rep stos"X"\n\n" \
                             : "+D"(dst), "+c"(c) :: "rax", "flags"); \  
        } while (0)  
    if (size % 8 == 0)      STOS("q", 8);  
    else if (size % 4 == 0) STOS("l", 4);  
    else if (size % 2 == 0) STOS("w", 2);  
    else                    STOS("b", 1);  
    #undef STOS  
    #else  
    memset(dst, 0, size*count);  
    #endif  
}

この例はテストセットアップで機能しますが、一般的には機能しないことに注意してください。GCCは方向フラグを変更できるため、cld指示が必要です。さらに、命令によって変更されること%rdiをgccに通知する必要があります。また、gccではレジスタが入力とクローバーの両方であることを指定できないため、厄介な構文を使用する必要があります(これにより入力値も破損します) 。%rcxstos"+"

Nehalemで4サイクルのレイテンシーを持つ「cld」命令のため、これは最適ではありません。GCCはフラグレジスタの状態を内部で追跡するため(AFAICT)、毎回その命令を発行する必要はありません。

4

2 に答える 2

4

これを強制したいのであれば、インラインアセンブリをオプションとして除外するのはなぜですか?

#define my_forced_inline_memset(dst, c, N) \
   __asm__ __volatile__(                   \
       "rep stosq %%rax, (%%rdi)\n\t"
       : : "D"((dst)), "a"((c)), "c"((N)) : "memory");

これを次のようなデモ プログラムで使用します。

int main(int argc, char **argv)
{
    my_forced_inline_memset(argv[0], 0, argc);
    return 0;
}

このアセンブリを作成します:

00000000004004b0 <main>:
  4004b0:       89 f9                   mov    %edi,%ecx
  4004b2:       31 c0                   xor    %eax,%eax
  4004b4:       48 8b 3e                mov    (%rsi),%rdi
  4004b7:       f3 ab                   repz stos %rax,%es:(%rdi)
  4004b9:       c3                      retq

これは、GCC が別の方法を選択する理由の説明ではありませんが、前述のように、可能な動作を強制したい場合、およびこれが必要な場所を明示的に知っている場合は、ある種の特別に定義された関数を呼び出すことにほとんど問題はありません。独自の memset ?

注: repz stos %rax,(%rdi) (または同等の Intel 構文QWORD PTR)の粒度は 1 バイトであるため、は と同じではありません。そのため、上記はむしろ同じです。これを覚えておいてください。memset()memset()memset(..., c, N * 8)

編集:コードを次のように書く場合:

#include <stdint.h>                        // for uintptr_t
#define my_forced_inline_memset(dst, c, N)                            \
   __asm__ __volatile__(                                              \
       "rep stos %1, (%0)\n\t"                                        \
       :: "D"((dst)), "a"((uintptr_t)(c)), "c"((N)/sizeof(uintptr_t)) \
       : "memory");

32 ビットと 64 ビットの両方でコンパイルされます。

于 2012-05-29T16:50:15.447 に答える
2

私はGCCについて知りませんが、MSVCの新しいビルドでは、ループを使用して設定/コピーを強制的に使用しましたREP STOS(そして、既知のサイズと自動ベクトル化の最適化を引き続き許可します)、GCCで試してみるとうまくいくかもしれません.

GCC に に似たビルトインがあるかどうかを確認する代わりに、__stosqインライン アセンブリに移動する必要がありますが、GCC ではまったく悪くありません (おそらく最も簡単で最速の方法です)。

SETCCあなたの2番目の質問は、実際に良い答えを得るための一般的な方法です。これは、当面のケースに依存するためです。ただし、GCCは、特定のコーナーケース( / MOVCC/を使用)を除いて、ブランチを最適化するのに十分なはずですFMOVCC

于 2012-05-26T05:56:06.637 に答える