【问题标题】:Optimization of virtual function call虚函数调用的优化
【发布时间】:2012-09-22 12:42:14
【问题描述】:

我有一个关于优化虚函数调用的问题。我在某处读到过(问题是我现在找不到这篇文章),可以通过使用类似于以下的构造来优化 v-table 查找:

// Base.h
class Base
{
public:
    virtual void Foo() = 0;
};

// Concrete.h
class Concrete : public Base
{
public:
    virtual void Foo()
    {
        // do something;
    }
};

//Some.h
extern Base* const g_object;

// Some.cpp
Concrete on_stack_concrete;

Base* const g_object = &on_stack_concrete;

技巧应该是使用一个指向分配在堆栈上的变量的 const 指针(不是动态的),并且编译器肯定会优化它。因此,每当用户调用 g_object->Foo() 时, //do something 部分将被执行,而无需查找 v-table。

这是真的还是假的?

提前感谢您的重播。

编辑:

这种结构的可能用途是限制具体实现的接口。当然,人们可以争辩说“受限”方法应该是私有的,但有时库的其他模块需要访问对象的这些公共附加方法,而不允许用户操作这些方法。因此,例如使用#defines,可以创建类似于以下的代码:

// Some.cpp
#ifdef _WIN32
Win32Concrete concrete;
#elif defined _UNIX
UnixConcrete concrete;
#endif

Base* const g_global = &concrete;

事实上,这些类的声明只能在 CPP 文件中定义,因此用户不知道它们的存在。

问题不是为什么首先要使用这种常量指针,而是在这种情况下是否可以优化 v-table 查找。

【问题讨论】:

  • 这可能与编译器有关,您应该能够查看编译器生成的程序集来验证这一点
  • 指针总是指向同一个对象,为什么还要使用它?
  • @BoPersson,我现在能想到一个很好的应用程序,例如跨平台库,它只向用户公开抽象接口,后端根据一些#defines 为各种平台使用多种实现.这当然需要针对具有不同定义集的每个平台进行多次编译,但对于最终用户来说仍然没有区别。
  • 如果 Win32Concrete 类对 Some.h 的客户端不可见,那么他们不太可能优化 vtable,因为他们看不到它“真正是什么”,因为你不直到链接时间才决定。无论如何,不​​能保证 vtable 优化,所以除了一开始没有 vtable 之外,没有办法强制它。

标签: c++ optimization virtual-functions vtable


【解决方案1】:

你似乎误用了virtual

virtual 实现运行时多态性。而且您描述的场景不需要或不需要。 Win32ConcreteUnixConcrete 不太可能同时存在于任何编译环境中。

代替:

// Some.cpp
#ifdef _WIN32
Win32Concrete concrete;
#elif defined _UNIX
UnixConcrete concrete;
#endif

Base* const g_global = &concrete;

使用:

// CommonHeader.h
#ifdef _WIN32
typedef Win32Concrete Concrete;
#elif defined _UNIX
typedef UnixConcrete Concrete;
#endif

现在你的函数不需要是虚拟的了。

【讨论】:

  • 以这种方式初始化的全局变量的要点是避免将其作为参数传递给任何需要的函数,因此不会发生这种情况。它是全球性的,因此在任何地方都可以访问,因此您提到的“上下文”在我的理解中不会改变。
  • @AdrianLis 如果您直接访问全局变量,那么为什么还要创建 Base* const g_object 呢?您可以改用on_stack_concrete.foo()。在这种情况下,您可以避免查找。
  • 因为具体类可以向用户公开他不应该直接使用的不同(附加)接口。这就是为什么指向基类的指针到位,将接口仅限于重要的东西。我将编辑问题以显示可能的用法。
  • 我知道这样的解决方案,但是您先生,再次错过了外部曝光的一个重要点。 Base 是图书馆用户的公共界面。具体的实现是库内部实现的细节,每个都可以有很大的不同。当然,在内部我确实使用了需要“更丰富”接口的具体变量,但是无论内部实现使用什么,用户都应该获得统一的接口,并且由于这在运行时不会改变并且是以这种方式编译的,所以问题是如果 v-table 查找是不可取的。
  • 当然,我可以将适配器或包装器用于限制接口的具体实现,但这需要为每个新实现执行大量预处理宏和额外编码。在这种情况下使用运行时多态性更容易,但会产生不必要的虚函数成本。这就是为什么我问的是我读到的关于可行选项的解决方案
【解决方案2】:

这是 Doom 3 源代码 (https://github.com/id-Software/DOOM-3-BFG/) 中使用的方法,例如 neo/framework/FileSystem.h 定义了这个:

extern idFileSystem *       fileSystem;

neo/framework/FileSystem.cpp 定义了这个:

idFileSystemLocal   fileSystemLocal;
idFileSystem *      fileSystem = &fileSystemLocal;

我能找到的唯一讨论是:http://fabiensanglard.net/doom3/

idTech4 高级对象都是带有虚拟方法的抽象类。这通常会影响性能,因为在运行时调用它之前,必须在 vtable 中查找每个虚拟方法地址。但是有一个“技巧”可以避免这种情况。

由于在数据段中静态分配的对象具有已知类型,编译器可以在调用 commonLocal 方法时优化 vtable 查找。

【讨论】:

    【解决方案3】:

    解决这个问题的最简单方法是让需要访问受限方法的类成为 Concrete 类的朋友。然后他们可以完全访问课程,而其他人只能获得公共访问权限。

    如果这不切实际,您可以将实现放在所有受保护方法的基类中,然后派生一个公开受保护方法的特殊类。

    class Concrete
    {
    public:
        void foo() { ... }
    protected:
        void bar() { ... }
    };
    
    class ConcretePrivate : public Concrete
    {
    public:
        void bar() { Concrete:: bar(); }
    };
    
    ConcretePrivate g_globalPrivate;
    Concrete& g_global = g_globalPrivate;
    

    使用g_global 的代码只能访问具体方法。使用g_globalPrivate 的代码可以访问ConcretePrivate 方法。

    【讨论】:

      猜你喜欢
      • 2018-11-21
      • 1970-01-01
      • 2017-10-03
      • 2011-08-30
      • 1970-01-01
      • 2021-02-13
      • 1970-01-01
      • 2012-02-06
      相关资源
      最近更新 更多