2012-08-10 11 views
8

Próbuję zmierzyć różnicę wydajności między korzystaniem z Boost.Variant i wirtualnymi interfejsami. Na przykład, przypuśćmy, że chcę zwiększać różne typy liczb równomiernie, używając Boost.Variant, użyłbym boost :: variant over int i float oraz statycznego gościa, który inkrementuje każdy z nich. Korzystając z interfejsów klas, używałbym czystego wirtualnego numeru klasy oraz klas number_int i number_float, które wywodzą się z niego i implementują metodę "inkrementacji".
Z moich testów korzystanie z interfejsów jest znacznie szybsze niż przy użyciu metody Boost.Variant. Pobiegłem kod na dole i otrzymał następujące wyniki:
wirtualna: 00: 00: 00,001028
Wariant: 00: 00: 00,012081
Wydajność wirtualnego interfejsu Boost.Variant Vs

Jak myślisz, dlaczego ta różnica jest? Myślałem, że Boost.Variant będzie o wiele szybszy.
** Uwaga: Zazwyczaj Boost.Variant używa alokacji sterty, aby zagwarantować, że wariant będzie zawsze niepusty. Ale czytałem w dokumentacji Boost.Variant, że jeśli boost :: has_nothrow_copy jest prawdziwe, to nie używa alokacji sterty, co powinno znacznie przyspieszyć działanie. Dla int i float boost :: has_nothrow_copy jest prawdziwe.

Oto mój kod do mierzenia obu podejść względem siebie.

#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; 
} 

Odpowiedz

14

Dla zainteresowanych po Byłem trochę sfrustrowany, zdałem opcję -O2 do kompilatora i boost :: wariant był sposób szybszy niż wirtualne połączenia.
Dzięki

+0

Dzięki za zamieszczenie follow-up, jestem zainteresowany! –

+0

Jakie były twoje wyniki i jaki kompilator? Korzystanie z Boost 1.52 i Mingw 4.7 Dostaję wariant jest około 8 razy wolniejszy w trybie zwolnienia. O dziwo, '-O2' jest nieco szybsze niż' -O3';/ – AbstractDissonance

+0

Używam g ++ 4.7 i nie jestem pewien, która wersja boost, ale prawdopodobnie 1.5x. Podałem -O2 do kompilatora, a moje wyniki: Wirtualny: 00: 00: 00.018806 Wariant: 00: 00: 00.000001 W większości przypadków uzyskałbym 00:00:00 w wariancie, więc ustawiłem iterations_count na 10000000 Używam tego testu na procesorze Intel Core i7 2,8 GHz. –

4

Jest to oczywiste, że -O2 zmniejsza czas trwania wariantu, ponieważ cała ta pętla jest zoptymalizowana. Zmień realizację wrócić skumulowany wynik do rozmówcy, tak że optymalizator nie byłoby usunąć pętlę, a dostaniesz prawdziwą różnicę:

wyjściowa:
wirtualna: 00: 00: 00,000120 = 10000000
Wariant: 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; 
} 
+3

Czy to wciąż pokazuje wirtualne jest o 2 rzędy wielkości szybsze? –

+0

Czy na pewno kompilator nie devirtualizuje? – Brahim