2016-05-13 4 views
10

ich die Leistung der folgenden Methoden von C++ Polymorphismus Vergleich:Static Polymorphismus mit Boost Variante einzelnen Besucher vs Multi Besucher vs dynamische Polymorphie

Methode [1]. Statischer Polymorphismus unter Verwendung von Boost-Varianten mit einem separaten Besucher für jede Methode Methode [2]. Statischer Polymorphismus unter Verwendung von Boost-Varianten mit einem einzelnen Besucher, der eine andere Methode mit Methodenüberladung aufruft Methode [3]. Einfache alte dynamische Polymorphie

Plattform: - Intel x86 64 Bit Red Hat moderner Multi-Core-Prozessor, 32 GB RAM - gcc (GCC) 4.8.1 mit -O2 Optimierung - Erhöhung 1.6.0

einige Ergebnisse:

  • Methode [1] zu haben scheint deutlich outperform Methoden [2] und [3]
  • Methode [3] übertrifft Methode [2] die meiste Zeit

Meine Frage ist, warum Methode [2], wo ich einen Besucher benutze, aber Methodenüberladung verwenden, um die richtige Methode aufzurufen, schlechtere Leistung als virtuelle Methoden. Ich würde erwarten, dass der statische Polymorphismus besser ist als der dynamische Polymorphismus. Ich verstehe, dass es einige Kosten für den zusätzlichen Parameter gibt, der in Methode [2] übergeben wird, um die visit() -Methode der Klasse, die aufgerufen werden soll, und möglicherweise noch mehr Verzweigungen aufgrund von Methodenüberladung anzugeben? Aber shouldn "t noch diese virtuelle Methoden entwickeln

-Code ist unter:.

// qcpptest.hpp 

#ifndef INCLUDED_QCPPTEST_H 
#define INCLUDED_QCPPTEST_H 

#include <boost/variant.hpp> 

class IShape { 
public: 
    virtual void rotate() = 0; 
    virtual void spin() = 0; 
}; 

class Square : public IShape { 
public: 
    void rotate() { 
    // std::cout << "Square:I am rotating" << std::endl; 
    } 
    void spin() { 
    // std::cout << "Square:I am spinning" << std::endl; 
    } 
}; 

class Circle : public IShape { 
public: 
    void rotate() { 
    // std::cout << "Circle:I am rotating" << std::endl; 
    } 
    void spin() { 
    // std::cout << "Circle:I am spinning" << std::endl; 
} 
}; 

// template variation 

// enum class M {ADD, DEL}; 
struct ADD {}; 
struct DEL {}; 

class TSquare { 
    int i; 
public: 
    void visit(const ADD& add) { 
     this->i++; 
    // std::cout << "TSquare:I am rotating" << std::endl; 
    } 
    void visit(const DEL& del) { 
     this->i++; 
    // std::cout << "TSquare:I am spinning" << std::endl; 
    } 

    void spin() { 
     this->i++; 
    // std::cout << "TSquare:I am rotating" << std::endl; 
} 
    void rotate() { 
     this->i++; 
    // std::cout << "TSquare:I am spinning" << std::endl; 
} 
}; 

class TCircle { 
    int i; 
public: 
    void visit(const ADD& add) { 
     this->i++; 
    // std::cout << "TCircle:I am rotating" << std::endl; 
    } 
    void visit(const DEL& del) { 
     this->i++; 
    // std::cout << "TCircle:I am spinning" << std::endl; 
    } 
    void spin() { 
     this->i++; 
     // std::cout << "TSquare:I am rotating" << std::endl; 
    } 
    void rotate() { 
    this->i++; 
     // std::cout << "TSquare:I am spinning" << std::endl; 
    } 
}; 

class MultiVisitor : public boost::static_visitor<void> { 
public: 
    template <typename T, typename U> 

    void operator()(T& t, const U& u) { 
    // std::cout << "visit" << std::endl; 
    t.visit(u); 
    } 
}; 

// separate visitors, single dispatch 

class RotateVisitor : public boost::static_visitor<void> { 
public: 
    template <class T> 
    void operator()(T& x) { 
    x.rotate(); 
    } 
}; 

class SpinVisitor : public boost::static_visitor<void> { 
public: 
    template <class T> 
    void operator()(T& x) { 
    x.spin(); 
    } 
}; 

#endif 

// qcpptest.cpp 

#include <iostream> 
#include "qcpptest.hpp" 
#include <vector> 
#include <boost/chrono.hpp> 

using MV = boost::variant<ADD, DEL>; 
// MV const add = M::ADD; 
// MV const del = M::DEL; 
static MV const add = ADD(); 
static MV const del = DEL(); 

void make_virtual_shapes(int iters) { 
    // std::cout << "make_virtual_shapes" << std::endl; 
    std::vector<IShape*> shapes; 
    shapes.push_back(new Square()); 
    shapes.push_back(new Circle()); 

    boost::chrono::high_resolution_clock::time_point start = 
     boost::chrono::high_resolution_clock::now(); 

    for (int i = 0; i < iters; i++) { 
    for (IShape* shape : shapes) { 
     shape->rotate(); 
     shape->spin(); 
    } 
    } 

    boost::chrono::nanoseconds nanos = 
     boost::chrono::high_resolution_clock::now() - start; 
    std::cout << "make_virtual_shapes took " << nanos.count() * 1e-6 
      << " millis\n"; 
} 

void make_template_shapes(int iters) { 
    // std::cout << "make_template_shapes" << std::endl; 
    using TShapes = boost::variant<TSquare, TCircle>; 
    // using MV = boost::variant<M>; 

    // xyz 
    std::vector<TShapes> tshapes; 
    tshapes.push_back(TSquare()); 
    tshapes.push_back(TCircle()); 
    MultiVisitor mv; 

    boost::chrono::high_resolution_clock::time_point start = 
     boost::chrono::high_resolution_clock::now(); 

    for (int i = 0; i < iters; i++) { 
    for (TShapes& shape : tshapes) { 
     boost::apply_visitor(mv, shape, add); 
     boost::apply_visitor(mv, shape, del); 
     // boost::apply_visitor(sv, shape); 
    } 
    } 
    boost::chrono::nanoseconds nanos = 
     boost::chrono::high_resolution_clock::now() - start; 
    std::cout << "make_template_shapes took " << nanos.count() * 1e-6 
      << " millis\n"; 
} 

void make_template_shapes_single(int iters) { 
    // std::cout << "make_template_shapes_single" << std::endl; 
    using TShapes = boost::variant<TSquare, TCircle>; 
    // xyz 
    std::vector<TShapes> tshapes; 
    tshapes.push_back(TSquare()); 
    tshapes.push_back(TCircle()); 
    SpinVisitor sv; 
    RotateVisitor rv; 

    boost::chrono::high_resolution_clock::time_point start = 
     boost::chrono::high_resolution_clock::now(); 

    for (int i = 0; i < iters; i++) { 
    for (TShapes& shape : tshapes) { 
     boost::apply_visitor(rv, shape); 
     boost::apply_visitor(sv, shape); 
    } 
    } 
    boost::chrono::nanoseconds nanos = 
     boost::chrono::high_resolution_clock::now() - start; 
    std::cout << "make_template_shapes_single took " << nanos.count() * 1e-6 
      << " millis\n"; 
} 

int main(int argc, const char* argv[]) { 
    std::cout << "Hello, cmake" << std::endl; 

    int iters = atoi(argv[1]); 

    make_virtual_shapes(iters); 
    make_template_shapes(iters); 
    make_template_shapes_single(iters); 

    return 0; 
} 
+0

Dieses Programm segmentiert für mich, wenn es mit "-O3" kompiliert wird. Bist du sicher, dass deine Logik stimmt? –

+1

Nur segfaults, wenn kein argv [1] bereitgestellt wird :) –

+0

Ja, Sie müssen ein Argument wie 10 oder 1000 oder 1000000 angeben. So oft wird die Schleife ausgeführt. – Sid

Antwort

4

Methode 2 grundsätzlich dynamische Dispatch uneffizient ist Neuimplementierung Wenn Sie:

shape->rotate(); 
shape->spin(); 

Das ist beinhaltet Nachschlagen der rechte Funktion in der Vtable und aufrufend. Die Ineffizienz von dieser Nachschau. Aber wenn Sie haben:

Die grob in explodiert (unter der Annahme eine add<> Funktionsvorlage Element, das nur ein reinterpret_cast, ohne zu überprüfen ist):

if (shape.which() == 0) { 
    if (add.which() == 0) { 
     mv(shape.as<TSquare&>(), add.as<ADD&>()); 
    } 
    else if (add.which() == 1) { 
     mv(shape.as<TSquare&>(), add.as<DEL&>()); 
    } 
    else { 
     // ??? 
    } 
} 
else if (shape.which() == 1) { 
    if (add.which() == 0) { 
     mv(shape.as<TCircle&>(), add.as<ADD&>()); 
    } 
    else if (add.which() == 1) { 
     mv(shape.as<TCircle&>(), add.as<DEL&>()); 
    } 
    else { 
     // ??? 
    } 
} 
else { 
    // ??? 
} 

Hier haben wir eine kombinatorische Explosion von Zweigen (die wir nicht 1 in Methode zu tun haben) aber wir müssen tatsächlich jeden möglichen statischen Typ jeder Variante überprüfen, um herauszufinden, was wir tun mussten (was wir in Methode 3 nicht tun mussten). Und diese Zweige können nicht vorhergesagt werden, da Sie jedes Mal ein anderes nehmen, so dass Sie keinen Code pipettieren können, ohne zu einem quietschenden Halt zu kommen.

Die Überladung auf mv() ist kostenlos - es ist die herauszufinden, was wir anrufen mv mit dem ist nicht. Beachten Sie auch die Delta-Zeit, die auf die Veränderung einer der beiden Achsen happen basieren würde:

+---------------+----------------+----------------+----------+ 
|    | Method 1 | Method 2 | Method 3 | 
+---------------+----------------+----------------+----------+ 
| New Type | More Expensive | More Expensive | Free | 
| New Operation |  Free  | More Expensive | Free* | 
+---------------+----------------+----------------+----------+ 

Methode 1 auf das Hinzufügen neuer Typen teurer wird, weil wir müssen explizit alle unsere Typen iterieren. Das Hinzufügen neuer Operationen ist kostenlos, da es keine Rolle spielt, was die Operation ist.

Methode 3 ist frei, neue Typen hinzuzufügen und freizugeben, um neue Operationen hinzuzufügen - die einzige Änderung ist die Erhöhung von vtable. Dies hat aufgrund der Objektgröße einige Auswirkungen, ist jedoch im Allgemeinen kleiner als die erhöhte Iteration über Typen.

+0

Danke. Frage: 1. Wäre nicht auch apply_visitor() für Methode 1 zu überprüfen, obwohl es eine einzige Ebene wäre, um zu überprüfen, mit welcher Form der Besucher aufgerufen wird? Was bedeutet, Methode 2 "1 zusätzliche Kontrolle" hat: So Methode 1: if (shape.which() == 0) { ... } else if (shape.which() == 1) { ... } Methode 2: - wie Sie dargestellt, so 2 Kontrollen anstelle von 1. so eine zusätzliche "wenn" Bedingung ist das teuer? – Sid

+0

Mein Punkt ist, mit mehr als zwei Arten kann ich verstehen, dass es mehrere Vergleiche geben könnte, aber in meinem Beispiel gibt es nur 2 Arten daher Methode 2 sollte nur "1" mehr Vergleich als Methode 2 ergeben. Die Auswirkungen auf die Leistung scheint unverhältnismäßig groß zu sein. – Sid

+0

@Sid Nicht zwei Typen, zwei * Varianten *. Sie haben keinen Vergleich mehr, Sie haben einen verschachtelten Vergleich. Es ist nicht nur so, als ob man einen weiteren Typ in die Variante von Methode 1 einfügt. – Barry