0

C++ で、スクリプト言語と同様に機能するアプリケーションを作成したいと考えています。
「セットアップ時間」中の入力から、各変数が配置される大きなグローバル配列と、関数のシーケンスを別の配列に定義します ( "LogicElement") を呼び出します (使用する変数などのパラメーターを含みます)。

1 つの実装は次のようになります。

class LogicElement_Generic
{
public:
  virtual void calc() const = 0;
};

class LogicElement_Mul : public LogicElement_Generic
{
  int &to;
  const int &from1;
  const int &from2;

public:
  LogicElement_Mul( int &_to, const int &_from1, const int &_from2 ) : to(_to), from1(_from1), from2(_from2)
  {}

  void calc() const
  {
    to = from1 * from2;
  }
};

char globalVariableBuffer[1000]; // a simple binary buffer
LogicElement_Generic *le[10];

int main( void )
{
  // just a demo, this would be setup from e.g. an input file:
  int *to    = (int*)globalVariableBuffer;
  int *from1 = (int*)(globalVariableBuffer + sizeof(int));
  int *from2 = (int*)(globalVariableBuffer + 2*sizeof(int));

  *from1 = 2;
  *from2 = 3;

  le[0] = new LogicElement_Mul( *to, *from1, *from2 );

  // doing all calculations:
  // finally it would be a loop iterating over all calculation functions,
  // over and over again - the area in the code where all the resources
  // would be burned...
  le[0]->calc();

  return *to;
}

意図したとおりに機能しますが、作成されたアセンブリを見てください。

  78                    .section    .text._ZNK16LogicElement_Mul4calcEv,"axG",@progbits,_ZNK16LogicElement_Mul4calcEv,comdat
  79                    .align 2
  80                    .weak   _ZNK16LogicElement_Mul4calcEv
  82                _ZNK16LogicElement_Mul4calcEv:
  83                .LFB6:
  17:.../src/test.cpp ****   void calc() const
  84                    .loc 1 17 0
  85                    .cfi_startproc
  86 0000 55            pushq   %rbp
  87                .LCFI6:
  88                    .cfi_def_cfa_offset 16
  89                    .cfi_offset 6, -16
  90 0001 4889E5        movq    %rsp, %rbp
  91                .LCFI7:
  92                    .cfi_def_cfa_register 6
  93 0004 48897DF8      movq    %rdi, -8(%rbp)
  18:.../src/test.cpp ****   {
  19:.../src/test.cpp ****     to = from1 * from2;
  94                    .loc 1 19 0
  95 0008 488B45F8      movq    -8(%rbp), %rax
  96 000c 488B4008      movq    8(%rax), %rax
  97 0010 488B55F8      movq    -8(%rbp), %rdx
  98 0014 488B5210      movq    16(%rdx), %rdx
  99 0018 8B0A          movl    (%rdx), %ecx
 100 001a 488B55F8      movq    -8(%rbp), %rdx
 101 001e 488B5218      movq    24(%rdx), %rdx
 102 0022 8B12          movl    (%rdx), %edx
 103 0024 0FAFD1        imull   %ecx, %edx
 104 0027 8910          movl    %edx, (%rax)
  20:.../src/test.cpp ****   }
 105                    .loc 1 20 0
 106 0029 5D            popq    %rbp
 107                .LCFI8:
 108                    .cfi_def_cfa 7, 8
 109 002a C3            ret
 110                    .cfi_endproc

アセンブリ ライン 95 .. 104 を見ると、変数ごとに 3 つの間接指定が使用されていることがわかります。

コードのこの部分 (calc() メソッド) は最終的に非常に高速に呼び出されるため、(一般的な C/C++ によって) 可能な限り最小の CPU サイクルとメモリ帯域幅を使用したいと考えています。

また、必要なロックを制限するためにマルチスレッド アプローチでダブル バッファリングを実行できるように、レイアウトがまったく同じ 2 つの可変バッファを用意する (上記のコードには示されていません) ことも実現したいと考えています (正確な実装の詳細は、この質問)。

したがって、大きな質問は次のとおりです。

  • アーキテクチャを変更して、calc() でのメモリ間接の量を減らすにはどうすればよいですか?
    (変数配列のオフセット アドレスを取得するためのものと、変数自体を取得するためのものの 2 つだけを期待していましたが、上記のコードをオフセットを使用するように変更した私の実験では、事態はさらに悪化しました!)
  • クラスと LogicElements の配列をセットアップして、計算メソッドの呼び出しが最小限のリソースしか使用しないようにするためのより良い方法はありますか?
4

1 に答える 1

0

@Ed S のヒントのおかげで、参照から離れて変更しました (コンパイラがより適切に最適化できることを望んでいました)。

しかし、私が行ったさらに重要なステップは、最適化を有効にした後に生成されたアセンブリを比較することでした (単純な-O2ことでした)。
(インテリジェントなコンパイラーが愚かなプログラマーを修正するのではなく、生成された「純粋な」マシンコードをより明確に把握したかったので、最初はそうしませんでしたが、コンパイラーは「愚か」すぎるようです。 ..)

したがって、変数配列の現在の結果は非常に良好です。

class LogicElement_Generic
{
public:
  virtual void calc(void * const base) const = 0;
};

class LogicElement_Mul : public LogicElement_Generic
{
  int const to;
  int const from1;
  int const from2;

public:
  LogicElement_Mul( int const _to, int const _from1, int const _from2 ) : to(_to), from1(_from1), from2(_from2)
  {}

  void calc(void * const base) const
  {
    *((int*)(base+to)) = *((int*)(base+from1)) * *((int*)(base+from2));
  }
};

char globalVariableBuffer[1000]; // a simple binary buffer
LogicElement_Generic *le[10];

int main( void )
{
  int to    = 0;
  int from1 = sizeof(int);
  int from2 = 2*sizeof(int);

  *((int*)(globalVariableBuffer+from1)) = 2;
  *((int*)(globalVariableBuffer+from2)) = 3;

  le[0] = new LogicElement_Mul( to, from1, from2 );
  le[0]->calc(globalVariableBuffer);

  return *((int*)(globalVariableBuffer+to));
}

アセンブリの関連部分:

  17:.../src/test.cpp ****   void calc(void * const base) const
  12                    .loc 1 17 0
  13                    .cfi_startproc
  14                .LVL0:
  18:.../src/test.cpp ****   {
  19:.../src/test.cpp ****     *((int*)(base+to)) = *((int*)(base+from1)) * *((int*)(base+from2));
  15                    .loc 1 19 0
  16 0000 4863470C      movslq  12(%rdi), %rax
  17 0004 48634F10      movslq  16(%rdi), %rcx
  18 0008 48635708      movslq  8(%rdi), %rdx
  19 000c 8B0406        movl    (%rsi,%rax), %eax
  20 000f 0FAF040E      imull   (%rsi,%rcx), %eax
  21 0013 890416        movl    %eax, (%rsi,%rdx)
  20:.../src/test.cpp ****   }
  22                    .loc 1 20 0
  23 0016 C3            ret
  24                    .cfi_endproc

だから私は答えとして最初の質問を再確認します! :)

2番目はまだ開いています。
(ポインタ演算が有効な C++ である可能性があるため、今ではなおさらですが、非常に醜い...)

于 2012-08-08T21:01:23.440 に答える