【问题标题】:dynamic_cast overhead in C++C++ 中的 dynamic_cast 开销
【发布时间】:2011-09-08 14:39:44
【问题描述】:

我知道 dynamic_cast 有运行时检查,因此认为更安全(可以在失败时返回空指针)但比 static_cast 慢。但是两者之间的开销有多糟糕?

我真的应该考虑在循环中使用 static_cast 来解决常规大型项目中的性能问题吗?或者差异很小,只与特殊的实时程序有关。

【问题讨论】:

  • 问题没有意义。他们两个演员服务不同的目的。它们并不是真正可以互换的。
  • @MSalters:在某种程度上他们是。您可能有其他方法来推断对象的最派生类型,如果是这样,static_cast 可以作为在同一位置使用dynamic_cast 的“替代品”。

标签: c++ casting


【解决方案1】:

你有介绍过吗?

规则是:

  • 当您知道目标类型有效时使用static_cast
  • 当您不确定时使用dynamic_cast,并且您需要程序为您查找对象的运行时类型。

就这么简单。所有其他考虑都是次要的。

【讨论】:

  • 您可能还注意到,有时使用dynamic_cast 是一种代码味道,需要重新考虑您的设计。
  • @MarkB 我读了很多,但很少看到可行的替代方案。访问者模式是可能的,但如果可能的子类的 nr 不受限制(例如形状),IMO 非常棒。假设您有抽象图和节点类(总是成对的)。如果一个图用于寻路而一个图用于“状态查找”(例如 GOAP),您将如何避免在 say graph.IsEqual(node1, node2) 中进行转换。我将如何“设计”这种铸造需求?感觉好像我错过了一些明显的东西......
  • 看来事情可能没有那么简单。例如,使用 dynamic_cast 与使用 static_cast 和枚举来跟踪你的具体类型是什么?
【解决方案2】:

取决于动态转换如何进行其类安全/正确性检查。在我分析过的系统中,它可以很快变成大量的字符串比较。这是一个足够大的交易,我们几乎使用了一个 assert_cast 风格的系统,其中静态转换用于提高性能,而动态转换用于调试。

【讨论】:

  • 是的,我喜欢使用asserted_cast<U&>(t) 助手来处理这类事情。
【解决方案3】:

超大型 C++ 代码库(例如 Mozilla、OpenOffice)有禁用 RTTI 的习惯(因此无法使用dynamic_cast 和异常),因为仅包含 RTTI 数据的开销在可执行文件中 被认为是不可接受的。特别是,据报道,由于额外的动态重定位,它会导致启动时间大幅增加(我记得大约 10%)。

从未讨论过避免dynamic_cast 和异常所需的额外代码实际上是否更慢。

【讨论】:

    【解决方案4】:

    Tomalak Geret'kal 是对的,当你知道时使用static_cast,当你不知道时使用dynamic_cast。如果你想避免成本,你必须以你知道的方式来构建你的设计。在单独的容器中存储单独的类型会使您的循环逻辑更加复杂,但您可以使用模板算法来解决这个问题。

    对于简单的继承树,它非常快。如果您在具有虚拟继承的复杂层次结构中进行横向转换,那么它必须进行非平凡的搜索。

    例子:

    struct Base {virtual ~Base () {}};
    struct Foo : Base {};
    
    struct Bar1 : virtual Base {};
    struct Bar2 : virtual Base {};
    
    struct Baz : Bar1, Bar2 {};
    
    Base * a = new Foo ();
    Bar1 * b = new Baz ();
    
    dynamic_cast <Foo *> (a); // fast
    dynamic_cast <Bar2 *> (b); // slow
    

    性能很大程度上取决于编译器。测量,测量,测量!请记住,运行时类型信息通常会被排除在外,并且会保存在非本地内存中——您应该考虑缓存在循环中的作用。

    【讨论】:

    • 那么,你能说不使用虚拟继承就快吗?
    • @bitmask:“相当快”是斯普拉夫实际所说的。明智地,我认为,由于病态的深度继承层次结构可能会造成某些动态转换的情况,实现几乎必须搜索手头对象的父母(及其父母......),寻找与目标的匹配类型。所以它肯定不能完全“快”。
    【解决方案5】:

    我刚刚尝试了一个小的演员表基准(在我大约 3 岁的上网本上,所以数字很高,但还不错)。这是测试设置:

    class A {
      public:
        virtual ~A() {}
    };
    
    class B : public A {
    };
    
    #define IT(DO) \
        for (unsigned i(1<<30); i; i--) { \
          B* volatile b(DO); \
          (void)b; \
        }
    
    #define CastTest(CAST) IT(CAST<B*>(a))
    #define NullTest() IT(NULL)
    
    int main(int argc, char** argv) {
      if (argc < 2) {
        return 1;
      }
      A* a(new B());
      switch (argv[1][0]) {
        case 'd':
          CastTest(dynamic_cast)
          break;
        case 's':
          CastTest(static_cast)
          break;
        default:
          NullTest()
          break;
      }
      return 0;
    }
    

    我发现它高度依赖于编译器优化,所以这是我的结果:

    (见下文评估

    O0:

    g++ -O0 -Wall castbench.cpp; time ./a.out _; time ./a.out s; time ./a.out d
    
    real        0m7.139s
    user        0m6.112s
    sys         0m0.044s
    
    real        0m8.177s
    user        0m6.980s
    sys         0m0.024s
    
    real        1m38.107s
    user        1m23.929s
    sys         0m0.188s
    

    O1:

    g++ -O1 -Wall castbench.cpp; time ./a.out _; time ./a.out s; time ./a.out d
    
    real        0m4.412s
    user        0m3.868s
    sys         0m0.032s
    
    real        0m4.653s
    user        0m4.048s
    sys         0m0.000s
    
    real        1m33.508s
    user        1m21.209s
    sys         0m0.236s
    

    氧气:

    g++ -O2 -Wall castbench.cpp; time ./a.out _; time ./a.out s; time ./a.out d
    
    real        0m4.526s
    user        0m3.960s
    sys         0m0.044s
    
    real        0m4.862s
    user        0m4.120s
    sys         0m0.004s
    
    real        0m2.835s
    user        0m2.548s
    sys         0m0.008s
    

    O3:

    g++ -O3 -Wall castbench.cpp; time ./a.out _; time ./a.out s; time ./a.out d
    
    real        0m4.896s
    user        0m4.308s
    sys         0m0.004s
    
    real        0m5.032s
    user        0m4.284s
    sys         0m0.008s
    
    real        0m4.828s
    user        0m4.160s
    sys         0m0.008s
    

    编辑:评估

    对于一次演员表(在上面的测试中,我们总共有 2**30 演员表)我们在上面的最小示例中得到以下时间:

    -O0    71.66 ns
    -O1    71.86 ns
    -O2    -1.46 ns
    -O3    -0.11 ns
    

    负值可能是由于程序执行时的负载不同,并且小到足以被视为不重要(即 ==0)而被丢弃。由于这里没有开销,我们必须假设编译器足够聪明以优化转换,即使我们说b 是易失的。因此,唯一可靠的值是 70 ns 结果。

    【讨论】:

    • 你的程序不是很容易阅读,“真实”、“用户”和“系统”是什么意思?
    • 参见linux.die.net/man/1/time 以了解time 命令的说明。 --- 您可能只对user 时间感兴趣。
    猜你喜欢
    • 2021-07-26
    • 2011-01-16
    • 1970-01-01
    • 1970-01-01
    • 2011-07-21
    • 1970-01-01
    • 1970-01-01
    • 2011-05-05
    • 1970-01-01
    相关资源
    最近更新 更多