假设我在抽象基类指针 mypointer->foo() 上有一个虚拟函数调用 foo()。当我的应用程序启动时,根据文件的内容,它选择实例化特定的具体类并将 mypointer 分配给该实例。在应用程序的剩余生命周期中,mypointer 将always指向该具体类型的对象。我无法知道这个具体类型是什么(它可能由动态加载库中的工厂实例化)。我只知道在第一次创建具体类型的实例后,该类型将保持不变。指针可能并不总是指向同一个对象,但该对象将始终具有相同的具体类型。请注意,从技术上讲,类型是在“运行时”确定的,因为它基于文件的内容,但在“启动”(加载文件)之后,类型是固定的。
然而,在 C++ 中,每次在应用程序的整个持续时间内调用 foo 时,我都会支付虚拟函数查找成本。编译器无法优化查找,因为它无法知道具体类型在运行时不会改变(即使它是有史以来最令人惊奇的编译器,它也无法推测动态加载的行为图书馆)。在 Java 或 .NET 等 JIT 编译语言中,JIT 可以检测到同一类型被反复使用并执行内联缓存 http://en.wikipedia.org/wiki/Inline_caching。我基本上正在寻找一种方法来手动为 C++ 中的特定指针执行此操作。
C++ 有没有办法缓存这个查找?我意识到解决方案可能相当黑客。如果可以编写配置测试来发现 ABI/编译器的相关方面,使其“实际上可移植”,即使不是真正可移植的,我愿意接受 ABI/编译器特定的 hack。
更新:致反对者:如果这不值得优化,那么我怀疑现代 JIT 是否会这样做。您是否认为 Sun 和 MS 的工程师在实现内联缓存方面浪费了时间,并且没有对其进行基准测试以确保有所改进?
虚拟函数调用有两个成本:vtable 查找和函数调用。
vtable 查找已经由硬件处理。现代 CPU(假设您使用的不是非常简单的嵌入式 CPU)将在其分支预测器中预测虚拟函数的地址,并推测性地与数组查找并行执行它。事实上,vtable 查找与函数的推测执行并行发生,这意味着,在您描述的情况下在循环中执行时,与直接的非内联函数调用相比,虚拟函数调用的开销几乎为零。
我过去实际上已经测试过这个,尽管是使用 D 编程语言,而不是 C++。当在编译器设置中禁用内联并且我在循环中调用同一函数数百万次时,无论该函数是否为虚拟函数,时间都在彼此的 epsilon 范围内。
虚拟函数的第二个也是更重要的成本是它们在大多数情况下阻止函数的内联。这比听起来更重要,因为内联是一种优化,可以实现其他几种优化,例如在某些情况下进行常量折叠。没有办法在不重新编译代码的情况下内联函数。 JIT 可以解决这个问题,因为它们在应用程序执行期间不断重新编译代码。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)