作为系统设计的一部分,我们需要实现工厂模式。结合工厂模式,我们还使用 CRTP 来提供一组基本功能,然后可以由派生类进行自定义。
示例代码如下:
class FactoryInterface{
public:
virtual void doX() = 0;
};
//force all derived classes to implement custom_X_impl
template< typename Derived, typename Base = FactoryInterface>
class CRTP : public Base
{
public:
void doX(){
// do common processing..... then
static_cast<Derived*>(this)->custom_X_impl();
}
};
class Derived: public CRTP<Derived>
{
public:
void custom_X_impl(){
//do custom stuff
}
};
虽然这个设计很复杂,但它确实提供了一些好处。可以内联初始虚函数调用之后的所有调用。派生类 custom_X_impl 调用也很高效。
我编写了一个比较程序来比较使用函数指针和虚函数的类似实现(紧密循环、重复调用)的行为。这种设计在带有 O2 和 O3 的 gcc/4.8 中取得了胜利。
一位 C++ 大师昨天告诉我,考虑到缓存未命中,大型执行程序中的任何虚函数调用都可能花费可变时间,我可以使用 C 样式函数表查找和 gcc 热列表实现潜在更好的性能功能。但是,在我上面提到的示例程序中,我仍然看到 2 倍的成本。
我的问题如下: 1. 大师的说法是真的吗?对于这两个答案,有没有我可以引用的链接。 2. 是否有任何我可以引用的低延迟实现,有一个基类调用派生类中的自定义函数,使用函数指针? 3. 有什么改进设计的建议吗?
随时欢迎任何其他反馈。
最佳答案
您的大师指的是 gcc 编译器的热属性。这个的效果attribute是:
The function is optimized more aggressively and on many targets it is placed into a special subsection of the text section so all hot functions appear close together, improving locality.
所以是的,在一个非常大的代码库中,热列表函数可能会保留在缓存中准备好立即执行,因为它避免了缓存未命中。
你可以完美地将这个属性用于成员函数:
struct X {
void test() __attribute__ ((hot)) {cout <<"hello, world !\n"; }
};
但是...
当您使用虚函数时,编译器通常会生成一个 vtable在类的所有对象之间共享。该表是指向函数的指针表。事实上——你的大师是对的——没有什么能保证这个表保留在缓存内存中。
但是,如果您手动创建一个“C 风格”的函数指针表,问题是完全一样的。虽然该函数可能保留在缓存中,但没有什么能确保您的函数表也保留在缓存中。
这两种方法的主要区别在于:
在虚函数的情况下,编译器知道虚函数是一个热点,并且可以决定确保将 vtable 也保存在缓存中(我不知道 gcc 是否可以做到这一点或者是否有这样做的计划)。
在手动函数指针表的情况下,您的编译器不会轻易推断出该表属于热点。因此,这种手动优化的尝试很可能适得其反。
我的观点:永远不要尝试优化编译器可以做得更好的东西。
结论
相信您的基准。相信你的操作系统:如果你的函数或数据被频繁访问,现代操作系统很可能会在其虚拟内存管理以及编译器生成的任何内容中考虑到这一点。
关于C++ 低延迟设计 : Function Dispatch v/s CRTP for Factory implementation,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29052374/