C++ 低延迟设计:Function Dispatch v/s CRTP for Factory implementation
C++ Low latency Design: Function Dispatch v/s CRTP for Factory implementation
作为系统设计的一部分,我们需要实现工厂模式。结合工厂模式,我们还使用 CRTP 来提供一组基本功能,然后可以由 Derived classes 进行自定义。
下面的示例代码:
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
}
};
虽然这个设计很复杂,但它确实提供了一些好处。可以内联初始虚函数调用之后的所有调用。派生的 class custom_X_impl 调用也很有效。
我编写了一个比较程序来比较使用函数指针和虚函数的类似实现(紧密循环、重复调用)的行为。这个设计在 gcc/4.8 与 O2 和 O3 的比赛中取得了胜利。
一位 C++ 大师昨天告诉我,考虑到缓存未命中,大型执行程序中的任何虚函数调用都可能花费可变时间,我可以使用 C 样式函数实现更好的性能 table 看看-ups 和 gcc 函数热列表。但是,我在上面提到的示例程序中仍然看到 2 倍的成本。
我的问题如下:
1. 大师的说法是真的吗?对于这两个答案,有没有我可以参考的链接。
2. 是否有任何我可以参考的低延迟实现,有一个基 class 在派生 class 中调用自定义函数,使用函数指针?
3. 有什么改进设计的建议吗?
随时欢迎任何其他反馈。
大佬指的是gcc编译器的hot属性。这个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,它在 class 的所有对象之间共享。这个 table 是一个 table 函数指针。事实上——你的大师是对的——没有任何东西保证这个 table 保留在缓存内存中。
但是,如果您手动创建一个 "C-style" table 函数指针,问题是完全一样的。虽然该函数可能保留在缓存中,但没有什么可以确保您的函数 table 也保留在缓存中。
这两种方法的主要区别在于:
在虚函数的情况下,编译器知道虚函数是一个热点,并且可以决定确保将 vtable 也保存在缓存中(我不知道 gcc 是否可以这样做,或者是否有这样做的计划)。
在手动函数指针 table 的情况下,您的编译器不会轻易推断出 table 属于热点。因此,这种手动优化的尝试很可能适得其反。
我的观点:永远不要尝试优化编译器可以做得更好的东西。
结论
相信您的基准。相信你的 OS:如果你的函数或数据被频繁访问,现代 OS 很有可能会在其虚拟内存管理以及编译器生成的任何内容中考虑到这一点。
作为系统设计的一部分,我们需要实现工厂模式。结合工厂模式,我们还使用 CRTP 来提供一组基本功能,然后可以由 Derived classes 进行自定义。
下面的示例代码:
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
}
};
虽然这个设计很复杂,但它确实提供了一些好处。可以内联初始虚函数调用之后的所有调用。派生的 class custom_X_impl 调用也很有效。
我编写了一个比较程序来比较使用函数指针和虚函数的类似实现(紧密循环、重复调用)的行为。这个设计在 gcc/4.8 与 O2 和 O3 的比赛中取得了胜利。
一位 C++ 大师昨天告诉我,考虑到缓存未命中,大型执行程序中的任何虚函数调用都可能花费可变时间,我可以使用 C 样式函数实现更好的性能 table 看看-ups 和 gcc 函数热列表。但是,我在上面提到的示例程序中仍然看到 2 倍的成本。
我的问题如下: 1. 大师的说法是真的吗?对于这两个答案,有没有我可以参考的链接。 2. 是否有任何我可以参考的低延迟实现,有一个基 class 在派生 class 中调用自定义函数,使用函数指针? 3. 有什么改进设计的建议吗?
随时欢迎任何其他反馈。
大佬指的是gcc编译器的hot属性。这个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,它在 class 的所有对象之间共享。这个 table 是一个 table 函数指针。事实上——你的大师是对的——没有任何东西保证这个 table 保留在缓存内存中。
但是,如果您手动创建一个 "C-style" table 函数指针,问题是完全一样的。虽然该函数可能保留在缓存中,但没有什么可以确保您的函数 table 也保留在缓存中。
这两种方法的主要区别在于:
在虚函数的情况下,编译器知道虚函数是一个热点,并且可以决定确保将 vtable 也保存在缓存中(我不知道 gcc 是否可以这样做,或者是否有这样做的计划)。
在手动函数指针 table 的情况下,您的编译器不会轻易推断出 table 属于热点。因此,这种手动优化的尝试很可能适得其反。
我的观点:永远不要尝试优化编译器可以做得更好的东西。
结论
相信您的基准。相信你的 OS:如果你的函数或数据被频繁访问,现代 OS 很有可能会在其虚拟内存管理以及编译器生成的任何内容中考虑到这一点。