10

Boost.Variant を使用する場合と仮想インターフェイスを使用する場合のパフォーマンスの違いを測定しようとしています。たとえば、Boost.Variant を使用して、さまざまな種類の数値を均一にインクリメントしたいとします。int と float に対して boost::variant を使用し、それぞれをインクリメントする static ビジターを使用します。クラス インターフェイスを使用して、純粋な仮想クラス number と、そこから派生して「インクリメント」メソッドを実装する number_int および number_float クラスを使用します。

私のテストでは、インターフェイスを使用する方が Boost.Variant を使用するよりもはるかに高速です。一番下のコードを実行したところ、次の結果が得られました:
Virtual: 00:00:00.001028
Variant: 00:00:00.012081

この違いはなぜだと思いますか?Boost.Variant の方がはるかに高速だと思いました。

** 注: 通常、Boost.Variant はヒープ割り当てを使用して、バリアントが常に空でないことを保証します。しかし、Boost.Variant のドキュメントを読んで、boost::has_nothrow_copy が true の場合、ヒープ割り当てを使用しないため、処理が大幅に高速化されます。int および float の場合、boost::has_nothrow_copy は true です。

これは、2 つのアプローチを相互に測定するための私のコードです。

#include <iostream>

#include <boost/variant/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <boost/variant/apply_visitor.hpp>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>

#include <boost/format.hpp>

const int iterations_count = 100000;

// a visitor that increments a variant by N
template <int N>
struct add : boost::static_visitor<> {
    template <typename T>    
    void operator() (T& t) const {
        t += N;
    }
};

// a number interface
struct number {        
    virtual void increment() = 0;
};

// number interface implementation for all types
template <typename T>
struct number_ : number {
    number_(T t = 0) : t(t) {}
    virtual void increment() {
        t += 1;
    }
    T t;
};

void use_virtual() {
    number_<int> num_int;
    number* num = &num_int;

    for (int i = 0; i < iterations_count; i++) {
        num->increment();
    }
}

void use_variant() {
    typedef boost::variant<int, float, double> number;
    number num = 0;

    for (int i = 0; i < iterations_count; i++) {
        boost::apply_visitor(add<1>(), num);
    }
}

int main() {
    using namespace boost::posix_time;

    ptime start, end;
    time_duration d1, d2;

    // virtual
    start = microsec_clock::universal_time();
    use_virtual();
    end = microsec_clock::universal_time();

    // store result
    d1 = end - start;

    // variant
    start = microsec_clock::universal_time();
    use_variant();
    end = microsec_clock::universal_time();

    // store result
    d2 = end - start;

    // output
    std::cout << 
        boost::format(
            "Virtual: %1%\n"
            "Variant: %2%\n"
        ) % d1 % d2;
}
4

2 に答える 2

15

興味のある人のために、少しイライラした後、オプション-O2をコンパイラに渡し、boost::variantは仮想呼び出しよりもはるかに高速でした。
ありがとう

于 2012-08-19T17:59:29.900 に答える
4

これは、ループ全体が最適化されて除去されるため、-O2 によってバリアント時間が短縮されることは明らかです。オプティマイザーがループを削除しないように、累積された結果を呼び出し元に返すように実装を変更すると、実際の違いが得られます。

出力:
仮想: 00:00:00.000120 = 10000000
バリアント: 00:00:00.013483 = 10000000

#include <iostream>

#include <boost/variant/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <boost/variant/apply_visitor.hpp>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>

#include <boost/format.hpp>

const int iterations_count = 100000000;

// a visitor that increments a variant by N
template <int N>
struct add : boost::static_visitor<> {
    template <typename T>
    void operator() (T& t) const {
        t += N;
    }
};

// a visitor that increments a variant by N
template <typename T, typename V>
T get(const V& v) {
    struct getter : boost::static_visitor<T> {
        T operator() (T t) const { return t; }
    };
    return boost::apply_visitor(getter(), v);
}

// a number interface
struct number {
    virtual void increment() = 0;
};

// number interface implementation for all types
template <typename T>
struct number_ : number {
    number_(T t = 0) : t(t) {}
    virtual void increment() { t += 1; }
    T t;
};

int use_virtual() {
    number_<int> num_int;
    number* num = &num_int;

    for (int i = 0; i < iterations_count; i++) {
        num->increment();
    }

    return num_int.t;
}

int use_variant() {
    typedef boost::variant<int, float, double> number;
    number num = 0;

    for (int i = 0; i < iterations_count; i++) {
        boost::apply_visitor(add<1>(), num);
    }

    return get<int>(num);
}
int main() {
    using namespace boost::posix_time;

    ptime start, end;
    time_duration d1, d2;

    // virtual
    start = microsec_clock::universal_time();
    int i1 = use_virtual();
    end = microsec_clock::universal_time();

    // store result
    d1 = end - start;

    // variant
    start = microsec_clock::universal_time();
    int i2 = use_variant();
    end = microsec_clock::universal_time();

    // store result
    d2 = end - start;

    // output
    std::cout <<
        boost::format(
            "Virtual: %1% = %2%\n"
            "Variant: %3% = %4%\n"
        ) % d1 % i1 % d2 % i2;
}
于 2015-03-29T15:23:41.873 に答える