当我知道类型时,如何避免虚拟呼叫?

时间:2013-10-21 15:21:59

标签: c++ optimization c++11 polymorphism virtual

请考虑以下代码段:

struct Base { virtual void func() { } };
struct Derived1 : Base { void func() override { print("1"); } };
struct Derived2 : Base { void func() override { print("2"); } };

class Manager {
    std::vector<std::unique_ptr<Base>> items;

    public:
        template<class T> void add() { items.emplace_back(new T); }
        void funcAll() { for(auto& i : items) i->func(); }
};

int main() {
    Manager m;
    m.add<Derived1>();
    m.add<Derived2>();
    m.funcAll(); // prints "1" and "2"
};

我正在使用virtual调度,以便从override多态对象中调用正确的std::vector方法。

但是,我知道多态对象的类型,因为我在Manager::add<T>中指定了它。

我的想法是通过获取成员函数virtual的地址并直接将其存储在某处来避免T::func()调用。但是这是不可能的,因为我需要将其存储为void*并将其转发回Manager::funcAll(),但我当时没有类型信息。

我的问题是:在这种情况下,似乎我有比平常更多的信息用于多态(用户在T中指定派生类型Manager::add<T>) - 有什么方法可以使用这种类型防止看似不必要的virtual电话的信息? (但是,用户应该能够在其代码中创建自己的Base派生类。)

3 个答案:

答案 0 :(得分:8)

  

但是,我知道多态对象的类型,因为我在Manager::add<T>中指定了它。

不,不。在add范围内,您知道要添加的对象的类型;但您可以像在示例中一样添加不同类型的对象。除非您将funcAll参数化为仅处理一种类型,否则Manager无法静态确定元素的类型。

如果您确实知道了类型,那么您可以非虚拟地调用该函数:

i->T::func();

但是,重申一下,你不能在这里静态地确定类型。

答案 1 :(得分:0)

如果我理解得很好,你需要你的add方法,即获取对象的类,根据该对象类将正确的函数存储在向量中。 你的向量只包含函数,没有关于对象的更多信息。

您希望在调用之前“解决”虚拟调用。 在下面的情况下,这可能很有趣:然后调用该函数很多次,因为每次都没有解决虚拟的开销。

因此,您可能希望使用与“虚拟”相似的过程,使用“虚拟表”。 虚拟的实现是在低级别完成的,因此与你想出的任何内容相比都非常快,所以同样,函数应该在它变得有趣之前调用很多次。

答案 2 :(得分:0)

在这种情况下有时可以帮助的一个技巧是按类型对向量进行排序(如果元素的顺序不对,您应该能够使用add()函数中可用类型的知识来强制执行此操作)否则很重要。如果您主要是为了调用虚函数而迭代向量,这将有助于CPU的分支预测器预测调用的目标。或者,您可以为管理器中的每种类型维护单独的向量,并依次迭代它们,这具有类似的效果。

您的编译器的优化器也可以帮助您使用这种代码,特别是如果它支持Profile Guided Optimization(POGO)。编译器可以在某些情况下对调用进行去虚拟化,或者POGO可以在生成的程序集中执行操作以帮助CPU的分支预测器,例如测试最常见的类型并对那些具有回退到间接调用的人执行直接调用。不常见的类型。

以下是测试程序的结果,它说明了按类型排序的性能优势,Manager是您的版本,Manager2维护一个由typeid索引的向量的哈希表:

Derived1::count = 50043000, Derived2::count = 49957000
class Manager::funcAll took 714ms
Derived1::count = 50043000, Derived2::count = 49957000
class Manager2::funcAll took 274ms
Derived1::count = 50043000, Derived2::count = 49957000
class Manager2::funcAll took 273ms
Derived1::count = 50043000, Derived2::count = 49957000
class Manager::funcAll took 714ms

测试代码:

#include <iostream>
#include <vector>
#include <memory>
#include <random>
#include <unordered_map>
#include <typeindex>
#include <chrono>

using namespace std;
using namespace std::chrono;

static const int instanceCount = 100000;
static const int funcAllIterations = 1000;
static const int numTypes = 2;

struct Base { virtual void func() = 0; };
struct Derived1 : Base { static int count; void func() override { ++count; } };
int Derived1::count = 0;
struct Derived2 : Base { static int count; void func() override { ++count; } };
int Derived2::count = 0;

class Manager {
    vector<unique_ptr<Base>> items;

public:
    template<class T> void add() { items.emplace_back(new T); }
    void funcAll() { for (auto& i : items) i->func(); }
};

class Manager2 {
    unordered_map<type_index, vector<unique_ptr<Base>>> items;

public:
    template<class T> void add() { items[type_index(typeid(T))].push_back(make_unique<T>()); }
    void funcAll() { 
        for (const auto& type : items) {
            for (auto& i : type.second) {
                i->func();
            }
        }
    }
};

template<typename Man>
void Test() {
    mt19937 engine;
    uniform_int_distribution<int> d(0, numTypes - 1);

    Derived1::count = 0;
    Derived2::count = 0;

    Man man;
    for (auto i = 0; i < instanceCount; ++i) {
        switch (d(engine)) {
        case 0: man.add<Derived1>(); break;
        case 1: man.add<Derived2>(); break;
        }
    }

    auto startTime = high_resolution_clock::now();
    for (auto i = 0; i < funcAllIterations; ++i) {
        man.funcAll();
    }
    auto endTime = high_resolution_clock::now();

    cout << "Derived1::count = " << Derived1::count << ", Derived2::count = " << Derived2::count << "\n"
         << typeid(Man).name() << "::funcAll took " << duration_cast<milliseconds>(endTime - startTime).count() << "ms" << endl;
}

int main() {
    Test<Manager>();
    Test<Manager2>();

    Test<Manager2>();
    Test<Manager>();
}