2013-06-12 11 views
7

再帰ラムダ関数は、通常の再帰関数と比較してオーバーヘッドを引き起こしますか?(それらをstd ::関数に取り込む必要があるため)
この関数と通常の関数のみを使用する同様の関数との違いは何ですか?それは、コンパイラによって自動的に作成されたいくつかのクラスの)(演算子を呼び出すことと同じになるよう再帰ラムダのオーバーヘッド

int main(int argc, const char *argv[]) 
{ 
    std::function<void (int)> helloworld = [&helloworld](int count) { 
     std::cout << "Hello world" << std::endl; 
     if (count > 1) helloworld(--count); 
    }; 
    helloworld(2); 
    return 0; 
} 
+2

あなたの主な問題はコンパイラの最適化がより少なくなるhttp://ideone.com/QsZVfH – stefan

+0

ニース!なぜアイデアはありますか?コンパイラは、このコードが通常の関数呼び出し(ノーキャプチャ、しかしそれ自体)に似ていると推測できませんでしたか? – 3XX0

+0

@ fscanそれは再帰的なラムダ関数なので、私はする必要があります。型推論はここでは動作しません – 3XX0

答えて

4

、それらは基本的にファンクタ自体であるが。 gccは、直接comparisonで見ることができるように最適化することができないようです。

ラムダの振る舞いを実装する、すなわちファンクタを作成することにより、gccが再び最適化されます。ラムダのあなたの具体的な例としては、私はファンクタはこのsecond demoでのようになります。作成した例えば

struct HelloWorldFunctor 
{ 
    void operator()(int count) const 
    { 
     std::cout << "Hello world" << std::endl; 
     if (count > 1) 
     { 
     this->operator()(count - 1); 
     } 
    } 
}; 
int main() 
{ 
    HelloWorldFunctor functor; 
    functor(2); 
} 

として実施することができます。

std::randのような不純な関数の呼び出しを導入したとしても、の再帰的なラムダやカスタムファンクタを使用しない場合の方が、さらに優れています。ここにはthird demoがあります。

結論:std::functionを使用すると、使用ケースによっては無視できるものの、オーバーヘッドがあります。このように使用するとコンパイラの最適化が妨げられるため、これを広範囲に使用すべきではありません。

+0

私はそれが非常にコンパイラ固有のものだと思うが、コードのおかげで、trueです。ideoneにコンパイラフラグを渡す方法はありますか? – Pedrom

+0

@Pedrom私はそう思わない。テル?免責事項:私はまだ集中的にラムダを使用していませんでした。私が作業しているプロジェクトでは、ラムダをサポートしていないコンパイラとの下位互換性が必要です。 – stefan

+0

マシン上でこれを検証しようとしていますが、何らかの理由でcygwinを使用した出力が表示されません:/ – Pedrom

2

C++でのラムダはファンクタに相当します。背後で何が起こっているのかを環境をキャプチャすると、キャプチャされた変数はクラスのコンストラクタに渡され、メンバ変数として格納されます。

要するに、パフォーマンスの差はゼロに非常に近いはずです。

は、ここではいくつかのさらなる説明があります:セクションへ

ジャンプ「はどのようにラムダクロージャは実装されています?」 http://www.cprogramming.com/c++11/c++11-lambda-closures.html

はEDIT:ステファンの答えとコードにいくつかの研究と感謝した後

、それが理由のstd ::機能イディオムの再帰ラムダ上のオーバーヘッドがあることが判明しました。ラムダは自身を呼び出すためにstd ::関数にラップする必要があるので、仮想関数を呼び出す必要があります。にオーバーヘッドを追加します。

主題はこの回答のコメントに扱われる:オーバーヘッドstd::functionとしてそれを格納することによって再帰的ラムダが使用される

https://stackoverflow.com/a/14591727/1112142
+0

ステファン・ラヴァヴェイ(Stephan Lavavej)も同様のアイデアを提示しています[http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/Stephan-T-Lavavej-Core-C-9- of-n)(0時38分20秒)。したがって、通常の関数を呼び出すとオーバーヘッドが少なくなると思われます。しかし、私はこのファンクタのアプローチが関数ポインタで最適化できることをどこかで覚えています。本当 ?私が提供したケースはどうですか? – 3XX0

+0

@ 3XX0それは素敵なビデオです、リンクのおかげで:)しかし、彼はそれを何度も繰り返し使用する予定があるなら、通常の関数を書くのが理にかなっていると言いました。コード、彼はパフォーマンスについて話していません。関数ポインタについては、* lambdasの代わりに*を使うこともできますが、便利であるとは思っていませんし、実際にはパフォーマンスの違いは見られませんでした。 – Pedrom

+0

@ 3XX0あなたの例では、最初の呼び出し時に*オーバーヘッドがあれば、関数hello_world(myfunc foo、int count)を呼び出すのとまったく同じです(それを渡すためにオブジェクトfooをインスタンス化する必要があるためです)このメソッドには、その後のすべての呼び出しは全く同じです。さらに、再帰関数がメソッドであれば、まったく違いはありません。 – Pedrom

3

したがって、std::functionは多形的に実装されます。あなたのコードを意味することとほぼ同等です:

struct B 
{ 
    virtual void do(int count) = 0; 
}; 

struct D 
{ 
    B* b; 
    virtual void do(int count) 
    { 
     if (count > 1) 
      b->do(count--); 
    } 
}; 

int main() 
{ 
    D d; 
    d.b = &d; 
    d.do(10); 
} 

仮想メソッドのルックアップはかなりのオーバーヘッドですが、あなたのアプリケーション領域に応じて、それは確かに可能であるように、タイトな十分な再帰を持っていることは稀です。