web-dev-qa-db-ja.com

std :: functionのパフォーマンスオーバーヘッドとは何ですか?

フォーラムでstd::function<>を使用するとパフォーマンスが低下すると聞きました。本当ですか?本当の場合、パフォーマンスが大幅に低下しますか?

59
user408141

ブーストの参考資料から情報を見つけることができます: boost :: function incur?を介した呼び出しのオーバーヘッドはどれくらいですか? および パフォーマンス

これは、機能を高めるための「はい」または「いいえ」を決定しません。パフォーマンスの低下は、プログラムの要件を考慮すると十分に許容される場合があります。多くの場合、プログラムの一部はパフォーマンスに重要ではありません。そして、それでも受け入れられるかもしれません。これはあなたが決定できるものです。

標準ライブラリバージョンに関しては、標準はインターフェイスのみを定義しています。動作させるのは完全に個々の実装次第です。 boostの機能と同様の実装が使用されると思います。

14
UncleBens

実際、std:functionにはパフォーマンスの問題があり、それを使用するときは常に考慮する必要があります。 std::functionの主な強み、つまりその型消去メカニズムは無料では提供されず、そのために代償を払うかもしれません(必ずしもそうする必要はありません)。

std::functionは、呼び出し可能な型をラップするテンプレートクラスです。ただし、呼び出し可能な型自体ではパラメーター化されず、戻り値および引数の型のみでパラメーター化されます。呼び出し可能な型は構築時にのみ知られているため、std::functionはこの型の事前宣言されたメンバーがコンストラクタに与えられたオブジェクトのコピーを保持することはできません。

大まかに言って(実際には、それよりも複雑です)std::functionは、コンストラクターに渡されたオブジェクトへのポインターのみを保持でき、これは生涯の問題を引き起こします。ポインタがstd::functionオブジェクトの寿命よりも短い寿命のオブジェクトを指す場合、内側のポインタはぶら下がります。この問題を防ぐために、std::functionは、operator new(またはカスタムアロケーター)を呼び出して、ヒープ上のオブジェクトのコピーを作成する場合があります。動的メモリ割り当ては、std::functionによって暗示されるパフォーマンスの低下として人々が最も言及するものです。

私は最近、より詳細な記事を書きました。それは、どのように(そしてどこで)メモリ割り当ての代価を支払うことを避けることができるかを説明しています。

http://drdobbs.com/cpp/232500059

74
Cassio Neri

これは、引数をバインドせずに関数を渡す(ヒープスペースを割り当てない)かどうかに大きく依存します。

他の要因にも依存しますが、これが主な要因です。

比較するものが必要なのは事実です。単に使用しない場合と比べて単に「オーバーヘッドを削減する」と言うことはできません。関数を渡す別の方法を使用する場合と比較する必要があります。そして、あなたがそれをまったく使わずに済ますことができれば、それは最初から必要ありませんでした

11
lurscher

まず、関数の内部でオーバーヘッドが小さくなります。ワークロードが高いほど、オーバーヘッドは小さくなります。

次に、g ++ 4.5は仮想関数と比較して違いを示しません。

main.cc

#include <functional>
#include <iostream>

// Interface for virtual function test.
struct Virtual {
    virtual ~Virtual() {}
    virtual int operator() () const = 0;
};

// Factory functions to steal g++ the insight and prevent some optimizations.
Virtual *create_virt();
std::function<int ()> create_fun();
std::function<int ()> create_fun_with_state();

// The test. Generates actual output to prevent some optimizations.
template <typename T>
int test (T const& fun) {
    int ret = 0;
    for (int i=0; i<1024*1024*1024; ++i) {
        ret += fun();
    }    
    return ret;
}

// Executing the tests and outputting their values to prevent some optimizations.
int main () {
    {
        const clock_t start = clock();
        std::cout << test(*create_virt()) << '\n';
        const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
        std::cout << "virtual: " << secs << " secs.\n";
    }
    {
        const clock_t start = clock();
        std::cout << test(create_fun()) << '\n';
        const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
        std::cout << "std::function: " << secs << " secs.\n";
    }
    {
        const clock_t start = clock();
        std::cout << test(create_fun_with_state()) << '\n';
        const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
        std::cout << "std::function with bindings: " << secs << " secs.\n";
    }
}

impl.cc

#include <functional>

struct Virtual {
    virtual ~Virtual() {}
    virtual int  operator() () const = 0;
};
struct Impl : Virtual {
    virtual ~Impl() {}
    virtual int  operator() () const { return 1; }
};

Virtual *create_virt() { return new Impl; }

std::function<int ()> create_fun() { 
    return  []() { return 1; };
}

std::function<int ()> create_fun_with_state() { 
    int x,y,z;
    return  [=]() { return 1; };
}

g++ --std=c++0x -O3 impl.cc main.cc && ./a.outの出力:

1073741824
virtual: 2.9 secs.
1073741824
std::function: 2.9 secs.
1073741824
std::function with bindings: 2.9 secs.

だから、恐れないで。仮想呼び出しよりもstd::functionを優先することで設計/保守性が向上する場合は、それらを試してください。個人的には、クラスのクライアントにインターフェイスと継承を強制しないというアイデアが本当に好きです。

10
Sebastian Mach