37

これは、複数回呼び出されたときに C++ ラムダが通常の関数よりも遅いのはなぜですか?で既に触れました。およびC++0x ラムダのオーバーヘッド しかし、私の例は前者の議論とは少し異なり、後者の結果と矛盾していると思います。

コードのボトルネックを探していると、値をバッファにコピーするなど、特定のプロセッサ関数で可変引数リストを処理する再帰的なテンプレート関数を見つけました。

template <typename T>
void ProcessArguments(std::function<void(const T &)> process)
{}

template <typename T, typename HEAD, typename ... TAIL>
void ProcessArguments(std::function<void(const T &)> process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

このコードをラムダ関数と一緒に使用するプログラムと、移動ポインターを使用してグローバル バッファーに引数をコピーするグローバル関数の実行時間を比較しました。

int buffer[10];
int main(int argc, char **argv)
{
  int *p = buffer;

  for (unsigned long int i = 0; i < 10E6; ++i)
  {
    p = buffer;
    ProcessArguments<int>([&p](const int &v) { *p++ = v; }, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  }
}

g++ 4.6 および -O3 でコンパイルされたツールの時間は、マシンで 6 秒以上かかります。

int buffer[10];
int *p = buffer;
void CopyIntoBuffer(const int &value)
{
  *p++ = value;
}

int main(int argc, char **argv)
{
  int *p = buffer;

  for (unsigned long int i = 0; i < 10E6; ++i)
  {
    p = buffer;
    ProcessArguments<int>(CopyIntoBuffer, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  }

  return 0;
}

約1.4秒かかります。

時間のオーバーヘッドを説明する舞台裏で何が起こっているのかわかりません。実行時に支払うことなくラムダ関数を利用するために何かを変更できるかどうか疑問に思っています。

4

1 に答える 1

56

ここでの問題は、std::function の使い方です。コピーで送信するため、その内容をコピーします(パラメーターを巻き戻すときに再帰的にそれを行います)。

さて、関数へのポインタの場合、コンテンツは、まあ、関数へのポインタです。ラムダの場合、内容は少なくとも関数へのポインター + キャプチャした参照です。これはコピーするのに 2 倍です。さらに、std::function の型消去のため、データのコピーはおそらく遅くなります (インライン化されません)。

ここにはいくつかのオプションがあり、おそらく std::function ではなくテンプレートを渡すのが最善でしょう。利点は、メソッド呼び出しがインライン化される可能性が高く、std::function による型消去が発生せず、コピーが発生せず、すべてが非常に優れていることです。そのように:

template <typename TFunc>
void ProcessArguments(const TFunc& process)
{}

template <typename TFunc, typename HEAD, typename ... TAIL>
void ProcessArguments(const TFunc& process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

2 番目のオプションも同じですが、コピーで送信processします。現在、コピーは行われていますが、それでもきれいにインライン化されています。

同様に重要なことはprocess、特に lamda の場合、' body もインライン化できることです。ラムダオブジェクトのコピーの複雑さとそのサイズに応じて、コピーによる受け渡しは参照による受け渡しよりも速い場合とそうでない場合があります。ローカルコピーよりもコンパイラが参照について推論するのが難しいため、高速になる可能性があります。

template <typename TFunc>
void ProcessArguments(TFunc process)
{}

template <typename TFunc, typename HEAD, typename ... TAIL>
void ProcessArguments(TFunc process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

3 番目のオプションは、std::function<> を参照渡ししてみてください。この方法では、少なくともコピーを回避できますが、呼び出しはインライン化されません。

これはパフォーマンスの結果です (ideones の C++11 コンパイラを使用)。予想どおり、インライン化されたラムダ本体が最高のパフォーマンスを発揮することに注意してください。

Original function:
0.483035s

Original lambda:
1.94531s


Function via template copy:
0.094748

### Lambda via template copy:
0.0264867s


Function via template reference:
0.0892594s

### Lambda via template reference:
0.0264201s


Function via std::function reference:
0.0891776s

Lambda via std::function reference:
0.09s
于 2013-09-04T17:03:24.317 に答える