当前位置: 首页 > 知识库问答 >
问题:

带有boost变量的静态多态性单访问者与多访问者与动态多态性

景德海
2023-03-14

我在比较C++多态性的以下方法的性能:

方法[1]。使用boost变体的静态多态性,每个方法都有一个单独的访问者method[2]。静态多态性使用boost变体,单个访问者调用不同的方法,使用方法重载方法[3]。平原老动态多态性

一些发现:

  • 方法[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
            << " 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;
}

共有1个答案

魏学智
2023-03-14

方法2基本上是低效率地重新实现动态调度。当您有:

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

这涉及到在vtable中查找正确的函数并调用它。查找的低效率。但是当你有:

boost::apply_visitor(mv, shape, add);

它大致分解为(假设add<>成员函数模板只是reexpert_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 {
   // ???
}
+---------------+----------------+----------------+----------+
|               |    Method 1    |    Method 2    | Method 3 |
+---------------+----------------+----------------+----------+
|    New Type   | More Expensive | More Expensive |   Free   |
| New Operation |      Free      | More Expensive |   Free*  |
+---------------+----------------+----------------+----------+

方法3可以自由地添加新类型,自由地添加新操作--唯一的变化是增加了vtable。由于对象大小的原因,这会有一些影响,但通常会小于在类型上增加的迭代。

 类似资料:
  • 如何从多个线程同时访问静态变量。 如果我有这样的课 我需要访问线程1的值,比如 从线程2中,我需要设置如下值 这会导致内存冲突吗?。如果是,建议使用什么方法来处理这种情况?。

  • 有人能提供一个简单的例子来解释Java中动态多态性和静态多态性之间的区别吗?

  • 问题内容: 我想了解参数多态性(例如Java / Scala / C ++语言中的通用类/函数的多态性)与Haskell类型系统中的“即席”多态性之间的主要区别。我熟悉第一种语言,但是我从未与Haskell合作。 更确切地说: 例如Java中的类型推断算法与Haskell中的类型推断有何不同? 请给我举一个例子,这种情况可以用Java / Scala编写但不能用Haskell编写(根据这些平台的模

  • 当我们使用有对象的子类型系统时,我们不能决定它的实际类型(例如,我们有一个包含许多不同类型对象的容器。但是,在这种情况下,为什么不尝试代数数据类型或联合类型来建模容器的元素类型呢?)。 我们只有对象,我们不知道它的方法的真实名称,所以我们必须使用vptr表来帮助我们。

  • 问题内容: 我已经定义了一个对象并声明了一个静态变量。在该方法中,当我尝试打印实例和类变量时,两者都打印相同的值。 不是实例变量吗?它应该打印0而不是50吗? 问题答案: 不,只有一个变量-您尚未声明任何实例变量。 不幸的是,Java允许您访问静态成员,就像通过相关类型的引用访问静态成员一样。这是IMO的设计缺陷,某些IDE(例如Eclipse)允许您将其标记为警告或错误- 但这是语言的一部分。您

  • 本文向大家介绍静态和动态多态性有什么区别?,包括了静态和动态多态性有什么区别?的使用技巧和注意事项,需要的朋友参考一下 多态是对象采取多种形式的能力。当使用父类引用来引用子类对象时,会在OOP中最常见地使用多态。被子类覆盖的方法称为运行时多态。JVM确定要在运行时而不是编译时执行的方法。在参数不同的同一类中,方法重载是静态多态的一个示例,编译器在编译时就知道要执行哪种方法。