c – GCCは、メモリに整列されたオブジェクトに対して効率的なコンストラクタを生成するように強制できますか?

私たちのアプリの最も内側のループの1つで呼び出されるコンストラクターを最適化しています。問題のクラスは約100バイト幅で、たくさんの整数、フロート、ブール、そして些細な構造から成り、そして些細にもコピー可能であるべきです(それは重要なデフォルトコンストラクタを持ちますが、デストラクタも仮想関数も持ちません)。このコントローラーに費やされるナノ秒単位の時間が、購入する必要がある約6,000ドルの余分なサーバーハードウェアに達するのに十分なほど頻繁に構成されています。

しかし、GCCはこのコンストラクタに対して非常に効率的なコードを発行していないことがわかります(たとえ-O3 -marchなどを設定しても)。初期化子リストを介してデフォルト値を埋める、GCCのコンストラクターの実装は、実行に約34nsかかります。このデフォルトコンストラクタの代わりに、さまざまなSIMD組み込み関数とポインタ演算を使ってオブジェクトのメモリ空間に直接書き込む手書きの関数を使用すると、構築には約8nsかかります。

これらのオブジェクトをSIMD境界上でメモリ整列させるときに、GCCにそのようなオブジェクト用の効率的なコンストラクタを発行させることはできますか。それとも、私は自分のメモリ初期化子をアセンブリで書くような昔ながらのテクニックに頼らなければなりませんか?

このオブジェクトはスタック上でローカルとして構築されるだけなので、new / mallocのオーバーヘッドは適用されません。

コンテキスト:

このクラスは、スタック上でローカル変数としてそれを構築し、デフォルトではない値を持ついくつかのフィールドを選択的に書き込み、それからそれを(参照によって)関数に渡し、他の関数に参照を渡します。

struct Trivial {
  float x,y,z;
  Trivial () : x(0), y(0), z(0) {};
};

struct Frobozz
{
   int na,nb,nc,nd;
   bool ba,bb,bc;
   char ca,cb,cc;
   float fa,fb;
   Trivial va, vb; // in the real class there's several different kinds of these
   // and so on
   Frobozz() : na(0), nb(1), nc(-1), nd(0),
               ba(false), bb(true), bc(false),
               ca('a'), cb('b'), cc('c'),
               fa(-1), fb(1.0) // etc
    {}
} __attribute__(( aligned(16) ));

// a pointer to a func that takes the struct by reference
typedef int (*FrobozzSink_t)( Frobozz& );

// example of how a function might construct one of the param objects and send it
// to a sink. Imagine this is one of thousands of event sources:
int OversimplifiedExample( int a, float b )
{
   Frobozz params; 
   params.na = a; params.fb = b; // other fields use their default values
   FrobozzSink_t funcptr = AssumeAConstantTimeOperationHere();
   return (*funcptr)(params);
}

ここでの最適なコンストラクタは、静的な「テンプレート」インスタンスから新しく構築されたインスタンスにコピーすることによって機能します。理想的には、SIMD演算子を使用して一度に16バイトを処理します。代わりに、GCCはOversimplifiedExample()に対してまったく間違ったことをします – バイト単位で構造体を埋めるための一連の即時移動です。

// from objdump -dS
int OversimplifiedExample( int a, float b )
{
     a42:55                   push   %ebp
     a43:89 e5                mov    %esp,%ebp
     a45:53                   push   %ebx
     a46:e8 00 00 00 00       call   a4b <_Z21OversimplifiedExampleif+0xb>
     a4b:5b                   pop    %ebx
     a4c:81 c3 03 00 00 00    add    $0x3,%ebx
     a52:83 ec 54             sub    $0x54,%esp
     // calling the 'Trivial()' constructors which move zero, word by word...
     a55:89 45 e0             mov    %eax,-0x20(%ebp)
     a58:89 45 e4             mov    %eax,-0x1c(%ebp)
     a5b:89 45 e8             mov    %eax,-0x18(%ebp)
     a5e:89 45 ec             mov    %eax,-0x14(%ebp)
     a61:89 45 f0             mov    %eax,-0x10(%ebp)
     a64:89 45 f4             mov    %eax,-0xc(%ebp)
     // filling out na/nb/nc/nd..
     a67:c7 45 c4 01 00 00 00 movl   $0x1,-0x3c(%ebp)
     a71:c7 45 c8 ff ff ff ff movl   $0xffffffff,-0x38(%ebp)
     a78:89 45 c0             mov    %eax,-0x40(%ebp)
     a7b:c7 45 cc 00 00 00 00 movl   $0x0,-0x34(%ebp)
     a82:8b 45 0c             mov    0xc(%ebp),%eax
     // doing the bools and chars by moving one immediate byte at a time!
     a85:c6 45 d0 00          movb   $0x0,-0x30(%ebp)
     a89:c6 45 d1 01          movb   $0x1,-0x2f(%ebp)
     a8d:c6 45 d2 00          movb   $0x0,-0x2e(%ebp)
     a91:c6 45 d3 61          movb   $0x61,-0x2d(%ebp)
     a95:c6 45 d4 62          movb   $0x62,-0x2c(%ebp)
     a99:c6 45 d5 63          movb   $0x63,-0x2b(%ebp)
     // now the floats...
     a9d:c7 45 d8 00 00 80 bf movl   $0xbf800000,-0x28(%ebp)
     aa4:89 45 dc             mov    %eax,-0x24(%ebp)
     // FrobozzSink_t funcptr = GetFrobozz();
     aa7:e8 fc ff ff ff       call   aa8 <_Z21OversimplifiedExampleif+0x68>
     // return (*funcptr)(params);
     aac:8d 55 c0             lea    -0x40(%ebp),%edx
     aaf:89 14 24             mov    %edx,(%esp)
     ab2:ff d0                call   *%eax
     ab4:83 c4 54             add    $0x54,%esp
     ab7:5b                   pop    %ebx
     ab8:c9                   leave 
     ab9:c3                   ret   
}

私はGCCがこのオブジェクトの単一の「デフォルトテンプレート」を作成し、それをデフォルトのコンストラクタにまとめてコピーすることを試みました。デフォルトではコピーするだけです。

struct Frobozz
{
     int na,nb,nc,nd;
     bool ba,bb,bc;
     char ca,cb,cc;
     float fa,fb;
     Trivial va, vb;
     inline Frobozz();
private:
     // and so on
     inline Frobozz( int dummy ) : na(0), /* etc etc */     {}
} __attribute__( ( aligned( 16 ) ) );

Frobozz::Frobozz( )
{
     const static Frobozz DefaultExemplar( 69105 );
     // analogous to copy-on-write idiom
     *this = DefaultExemplar;
     // or:
     // memcpy( this, &DefaultExemplar, sizeof(Frobozz) );
}

しかし、これは冗長なスタックコピーのせいで、初期化リストを使った基本的なデフォルトよりもさらに遅いコードを生成しました。

最後に、コンパイラ組み込み関数とメモリアラインメントに関する仮定を使用して、構造体を効率的にコピーするpipelined MOVDQA SSE2オペコードを使用して、* this = DefaultExemplarステップを実行するインラインfree関数を作成することにしました。これは私が必要とするパフォーマンスを手に入れました、しかしそれは気まぐれです。私はアセンブリで初期化子を書く私の時代が私の後ろにあったと思いました、そして私はむしろむしろGCCのオプティマイザが最初の場所で正しいコードを発行するようにしたいだけです。

GCCが自分のコンストラクタに最適なコードを生成する方法、コンパイラの設定、または見逃した追加の__attribute__方法はありますか。

これはUbuntu上で動作するGCC 4.4です。コンパイラフラグには、-m32 -march = core2 -O3 -fno-strict-aliasing -fPICなどがあります。移植性は考慮に入れられていません、そして私はここでパフォーマンスのために規格準拠を犠牲にして喜んでいます。

タイミングは、rdtscを使用してタイムスタンプカウンタを直接読み取ることによって、たとえば、タイマーの解像度とキャッシュおよび統計的有意性などに十分注意して、サンプル間のN OversimplifiedExample()呼び出しのループを測定することによって実行されました。

私はもちろんコールサイトの数をできるだけ減らすことでこれを最適化しました、しかし、私はまだ一般的にGCCからより良いユーザーを得る方法を知りたいです。

これが私のやり方です。コンストラクタを宣言しないでください。代わりに、デフォルト値を含む固定Frobozzを宣言してください。

const Frobozz DefaultFrobozz =
  {
  0, 1, -1, 0,        // int na,nb,nc,nd;
  false, true, false, // bool ba,bb,bc;
  'a', 'b', 'c',      // char ca,cb,cc;
  -1, 1.0             // float fa,fb;
  } ;

それからOversimplifiedExampleで:

Frobozz params (DefaultFrobozz) ;

gcc -O3(バージョン4.5.2)では、paramsの初期化は以下のようになります。

leal    -72(%ebp), %edi
movl    $_DefaultFrobozz, %esi
movl    $16, %ecx
rep movsl

それは32ビット環境で得られるのと同じくらい良いです。

警告:私はこれを64ビットgバージョン4.7.0 20110827(実験的)で試してみました、そしてそれはブロック移動の代わりに64ビットコピーの明白なシーケンスを生成しました。プロセッサはrep movsqを許可しませんが、rep movslは64ビットのロードとストアのシーケンスよりも高速であることを期待します。おそらくそうではありません。 (ただし、-Osスイッチ – スペースの最適化 – rep movsl命令を使用します。)とにかく、これを試して、何が起こるのかをお知らせください。

追加のために編集された:私は担当者movsqを許可していないプロセッサについて間違っていた。 Intelの文書には「MOVS、MOVSB、MOVSW、およびMOVSD命令の前にREPプレフィックスを付けることができます」と記載されていますが、これは単なる文書の誤りです。いずれにせよ、私がFrobozzを十分に大きくすると、64ビットコンパイラはrep movsq命令を生成します。だからそれはおそらくそれが何をしているのか知っている。

元のURL:https://stackoverflow.com/questions/8894695/can-gcc-be-coerced-to-generate-efficient-constructors-for-memory-aligned-objects

元のテキストを転載:c – GCCは、メモリに整列されたオブジェクトに対して効率的なコンストラクタを生成するように強制できますか?