【问题标题】:Does several levels of base classes slow down a class/struct in c++?多个级别的基类是否会减慢 C++ 中的类/结构?
【发布时间】:2008-09-19 04:01:59
【问题描述】:

拥有多个级别的基类会减慢一个类的速度吗? A派生B派生C派生D派生F派生G,...

多重继承会减慢类的速度吗?

【问题讨论】:

    标签: c++ oop


    【解决方案1】:

    非虚拟函数调用在运行时绝对不会影响性能,这符合 C++ 的口头禅,即您不应该为不使用的东西付费。 在虚函数调用中,您通常需要为额外的指针查找付费,无论您有多少继承级别或基类的数量。 当然这是所有实现定义的。

    编辑:如其他地方所述,在某些多重继承场景中,需要在调用之前调整“this”指针。 Raymond Chen 为 COM 对象描述了how this works。基本上,在从多个基类继承的对象上调用虚函数可能需要在虚调用所需的额外指针查找之上进行额外的减法和 jmp 指令。

    【讨论】:

    【解决方案2】:

    [深度继承层次结构]通过增加不必要的复杂性大大增加了维护负担,迫使用户学习许多类的接口,即使他们只想使用特定的派生类。通过将不必要的 vtable 和间接添加到真正不需要它们的类中,它还会对内存使用和程序性能产生影响。如果您发现自己经常创建深层继承层次结构,您应该检查您的设计风格,看看您是否已经养成了这个坏习惯。 很少需要深度层次结构,而且几乎从来都不是好的。如果您不相信这一点,但认为“没有大量继承,OO 就不是 OO”,那么需要考虑的一个很好的反例是[C++] 标准库本身。 --Herb Sutter

    【讨论】:

    • @Nescio “深度层次结构”有多深,比如超过 3 或 4 个类?
    【解决方案3】:
    • 多重继承会减慢速度吗 上课?

    正如多次提到的,深度嵌套的单一继承层次结构不应为虚拟调用带来额外的开销(高于任何虚拟调用的开销)。

    但是,当涉及到多重继承时,通过基类指针调用虚函数时,有时会产生非常小的额外开销。在这种情况下,一些实现让虚函数通过一个小的 thunk 来调整 'this' 指针,因为

    (static_cast<Base*>( this) == this)
    

    根据对象布局不一定正确。

    请注意,所有这些都非常非常依赖于实现。

    参见 Lippman 的“深入 C++ 对象模型”第 4.2 章 - MI 下的虚拟成员函数/虚拟函数

    【讨论】:

      【解决方案4】:

      不同级别的虚拟调用之间没有速度差异,因为它们都被展平到 vtable 中(指向被覆盖方法的最衍生版本)。因此,当 instB 的实例时调用 ((A*)inst)->Method() 与 inst 时的开销相同是 D 的一个实例。

      现在,虚拟调用比非虚拟调用更昂贵,但这是因为指针取消引用,而不是类层次结构实际深度的函数。

      【讨论】:

        【解决方案5】:

        虚拟调用本身比普通调用更耗时,因为它必须查找要从 vtable 调用的实际函数的地址

        此外,由于查找要求,内联等编译器优化可能难以执行。由于堆栈弹出和推送和跳转操作,内联本身不可能的情况会导致相当高的开销

        这是一项适当的研究,表明开销可能高达 50% http://www.cs.ucsb.edu/~urs/oocsb/papers/oopsla96.pdf

        这是另一个资源,该资源着眼于拥有大型虚拟类库http://keycorner.org/pub/text/doc/kde-slow.txt的副作用@

        具有多重继承的虚拟调用的分派是特定于编译器的,因此在这种情况下实现也会产生影响。

        关于拥有大量基类的具体问题,通常类对象的内存布局将具有其中所有其他组成类的 vtbl ptrs。

        查看此页面以获取示例 vtable 布局 - http://www.codesourcery.com/public/cxx-abi/cxx-vtable-ex.html

        因此,对由更深入层次结构的类实现的方法的调用仍然只是单个间接,而不是您似乎认为的多个间接。该调用不必从一个类导航到另一个类以最终找到要调用的确切函数。

        但是,如果您使用组合而不是继承,则每个指针调用都将是一个虚拟调用,并且会存在开销,并且如果在该虚拟调用中该类使用更多组合,则将进行更多虚拟调用。根据您拨打的电话数量,这种设计会更慢。

        【讨论】:

          【解决方案6】:

          几乎所有答案都指向虚拟方法在 OP 的示例中是否会更慢,但我认为 OP 只是在询问是否有多个级别的继承本身是否很慢。答案当然是否定的,因为这一切都发生在 C++ 的编译时。我怀疑这个问题是由脚本语言的经验驱动的,在这种语言中,此类继承图可以是动态的,在这种情况下,它可能会更慢。

          【讨论】:

            【解决方案7】:

            如果没有虚函数,那么它不应该。如果存在,则调用虚函数会对性能产生影响,因为这些虚函数是通过函数指针或其他间接方法调用的(取决于具体情况)。不过,我认为影响与继承层次的深度无关。

            Brian,请明确回答您的评论。如果继承树中的任何位置都没有虚函数,则不会影响性能。

            【讨论】:

            • 如果没有虚函数怎么办?
            • 那么就完全没有关系了,因为所有的函数调用都可以在编译时解决。 IE。编译器确切地知道要调用哪个函数,从而节省了 vtable 查找。
            【解决方案8】:

            在深度继承树中使用重要的构造函数会减慢对象的创建速度,因为每次创建子对象都会导致对所有父构造函数的函数调用一直到基层。

            【讨论】:

              【解决方案9】:

              是的,如果你这样引用它:

              // F is-a E,
              // E is-a D and so on
              
              A* aObject = new F(); 
              aObject->CallAVirtual();
              

              然后,您将使用指向 A 类型对象的指针。鉴于您正在调用一个虚拟函数,它必须查找函数表(vtable)以获取正确的指针。有一些开销,是的。

              【讨论】:

                【解决方案10】:

                调用虚函数比调用非虚函数稍慢。但是,我认为继承树的深度并不重要。

                但这不是您通常应该担心的差异。

                【讨论】:

                  【解决方案11】:

                  虽然我不完全确定,但我认为除非您使用虚拟方法,否则编译器应该能够很好地对其进行优化,以至于继承不应该太重要。

                  但是,如果您调用基类中的函数,而基类中的函数又会调用其基类中的函数,等等,这可能会影响性能。

                  本质上,这在很大程度上取决于您如何构建继承树。

                  【讨论】:

                    【解决方案12】:

                    正如Corey Ross 所指出的,对于任何叶派生类,vtable 在编译时都是已知的,因此无论层次结构如何,虚拟调用的成本都应该是相同的。

                    但是,dynamic_cast 不能这样说。如果您考虑如何实现dynamic_cast,基本方法是在您的层次结构中进行 O(n) 搜索!

                    在多继承层次结构的情况下,您还需要支付少量成本在层次结构中的不同类之间进行转换:

                    sturct A { int i; };
                    struct B { int j; };
                    
                    struct C : public A, public B { int k ; };
                    
                    // Let's assume that the layout of C is:  { [ int i ] [ int j ] [int k ] }
                    
                    void foo (C * c) {
                      A * a = c;                // Probably has zero cost
                      B * b = c;                // Compiler needed to add sizeof(A) to 'c'
                      c = static_cast<B*> (b);  // Compiler needed to take sizeof(A)' from 'b'
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2023-03-11
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-07-24
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多