静态多态性与boost变体单访问者与多访问者与动态多态性
Static Polymorphism with boost variant single visitor vs multi visitor vs dynamic polymorphism
我正在比较以下C++多态性方法的性能:
方法[1]。使用boost变体的静态多态性,每个方法都有一个单独的访问者方法[2]。使用boost变体的静态多态性与使用方法重载调用不同方法的单个访问者方法[3]。普通老动态多态性
平台:-Intel x86 64位Red Hat现代多核处理器,32 GB RAM-gcc(gcc)4.8.1与-O2优化-提升1.6.0
一些发现:
- 方法[1]似乎明显优于方法[2]和[3]
- 方法[3]在大多数情况下都优于方法[2]
我的问题是,为什么我使用访问者但使用方法重载来调用正确方法的方法[2]的性能比虚拟方法差。我希望静态多态性比动态多态性更好。我知道在方法[2]中传递额外参数以确定要调用类的哪一个visit()方法是有代价的,并且可能由于方法重载而导致更多分支?但这难道不应该仍然优于虚拟方法吗?
代码如下:
// 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
<< " millisn";
}
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
<< " millisn";
}
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
<< " millisn";
}
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;
}
方法2基本上是低效地重新实现动态调度。当你有:
shape->rotate();
shape->spin();
这涉及到在vtable中查找正确的函数并调用它。查找的效率很低。但当你有:
boost::apply_visitor(mv, shape, add);
它大致分解为(假设一个add<>
成员函数模板,它只是一个没有检查的reinterpret_cast
):
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 {
// ???
}
在这里,我们有一个分支的组合爆炸(在方法1中我们不必这样做),但实际上我们必须检查每个变体的每个可能的静态类型,以找出我们必须做的事情(在方法3中我们不必须这样做)。而且这些分支是无法预测的,因为你每次都要使用不同的分支,所以你无法在不急剧停止的情况下传输任何类型的代码。
mv()
上的重载是免费的——这是为了弄清楚我们用什么来调用mv
,而不是。还要注意,根据改变两个轴中的任何一个会发生的增量时间:
+---------------+----------------+----------------+----------+
| | Method 1 | Method 2 | Method 3 |
+---------------+----------------+----------------+----------+
| New Type | More Expensive | More Expensive | Free |
| New Operation | Free | More Expensive | Free* |
+---------------+----------------+----------------+----------+
方法1在添加新类型方面变得更加昂贵,因为我们必须显式地迭代所有类型。添加新操作是免费的,因为操作是什么并不重要。
方法3可以自由地添加新类型,也可以自由地增加新操作——唯一的变化是增加vtable。由于对象大小的原因,这将产生一些影响,但通常会小于对类型增加的迭代。
- 多态性和功能结合
- 找不到成员对象:没有名为get_event()的成员,也处理多态性和向量
- 使用取消引用的指针的多态性会产生意外的结果.为什么?
- C++boost序列化多态性问题
- 如何查找哪个类对象位于数组的特定索引上(多态性)
- 如何在多线程中正确使用unique_ptr进行多态性?
- 具有智能指针的多态性
- 在 C++ 中在堆栈上创建实例时如何保持多态性?
- 继承/多态性 - 我是否被迫使用"protected"变量?
- C++ 多态性在代码::块 17.12 中不起作用
- C++ 泛型和多态性:这种模式可行吗?
- 为什么我们实际上需要运行时多态性?
- 如何在这个简单的最小示例中实现多态性?
- 如何使用静态多态性在 int 和指针类型之间进行转换?
- 无法初始化已知大小的矢量指针,该大小不会因多态性而更改
- 运行时多态性和dynamic_cast需要澄清
- 如何调用指针类型的方法(禁用多态性)?
- 将静态访问者与静态多态性层次结构相耦合
- 静态多态性与boost变体单访问者与多访问者与动态多态性
- c++访问者模式和多态性