众所周知的解决方案,用于避免dynamic_cast的缓慢

Well-known solution for avoiding the slowness of dynamic_cast?

本文关键字:dynamic 缓慢 cast 解决方案 用于 众所周知      更新时间:2023-10-16

我需要运行时多态性,所以我使用了dynamic_cast.
但是现在我有两个问题 - dynamic_cast非常慢!(向下滚动查看基准测试。

长话短说,我最终以这种方式解决了问题,使用 static_cast

struct Base
{
    virtual ~Base() { }
    virtual int type_id() const = 0;
    template<class T>
    T *as()
    { return this->type_id() == T::ID ? static_cast<T *>(this) : 0; }
    template<class T>
    T const *as() const
    { return this->type_id() == T::ID ? static_cast<T const *>(this) : 0; }
};
struct Derived : public Base
{
    enum { ID = __COUNTER__ };  // warning: can cause ABI incompatibility
    int type_id() const { return ID; }
};
int main()
{
    Base const &value = Derived();
    Derived const *p = value.as<Derived>();  // "static" dynamic_cast
}

但我肯定不是第一个遇到这个问题的人,所以我认为可能值得一问:

与其提出这样的自制解决方案,不如将来可以使用一个众所周知的模式/库来解决此问题?

<小时 />

示例基准测试

要了解我在说什么,请尝试下面的代码 - dynamic_cast比我的机器上的仅virtual调用慢大约 15 倍(110 毫秒,而下面的代码为 1620 毫秒(:

#include <cstdio>
#include <ctime>
struct Base { virtual unsigned vcalc(unsigned i) const { return i * i + 1; } };
struct Derived1 : public Base 
{ unsigned vcalc(unsigned i) const { return i * i + 2; } };
struct Derived2 : public Derived1
{ unsigned vcalc(unsigned i) const { return i * i + 3; } };
int main()
{
    Base const &foo = Derived2();
    size_t const COUNT = 50000000;
    {
        clock_t start = clock();
        unsigned n = 0;
        for (size_t i = 0; i < COUNT; i++)
            n = foo.vcalc(n);
        printf("virtual call: %d ms (result: %u)n",
            (int)((clock() - start) * 1000 / CLOCKS_PER_SEC), n);
        fflush(stdout);
    }
    {
        clock_t start = clock();
        unsigned n = 0;
        for (size_t i = 0; i < COUNT; i++)
            n = dynamic_cast<Derived1 const &>(foo).vcalc(n);
        printf("virtual call after dynamic_cast: %d ms (result: %u)n",
            (int)((clock() - start) * 1000 / CLOCKS_PER_SEC), n);
        fflush(stdout);
    }
    return 0;
}

当我简单地删除单词 virtual 并将dynamic_cast更改为 static_cast 时,我得到了 79 毫秒的运行时间——所以虚拟调用只比静态调用慢 ~25%!

dynamic_cast的大多数用法都可以用双重调度(又名访客模式(代替。这相当于两个虚拟调用,根据您的基准测试,这仍然比 dynamic_cast 快 7.5 倍。

您可能对这个常量时间实现感兴趣:http://www.stroustrup.com/isorc2008.pdf

另请注意,许多上行转换在特定约束下可能会得到简化 - 例如,如果您不使用多重继承,仅使用浅继承,或者以其他方式保证类型没有共同的祖先,则某些评估可能会短路,并且不需要执行详尽的评估(如 dynamic_cast 提供(。

像往常一样,针对给定用例和实际类层次结构,根据供应商的实现来分析您的实现。