谈谈 内联 虚函数 和 CRTP

内联 虚函数 和 CRTP

虚函数 和 内联

出于一些系统设计的原因,导致我们在编写程序的时候通常是面向抽象编程的。比方说我们设计了一个计算框架,通常是面向一个接口编程的。

class Expression {
public:
    virtual void update(Context* context,DataType &input) = 0;
};

然后我们通常会把逻辑写在具体的实现中,例如一个计算sum聚合的算子:

class SumExpression: public Expression {
public:
	void update(Context* context,DataType &input) {
		(*(DataType*)context) += input;
	}
};

我们在实际使用的时候大多数情况会通过虚函数的方式来调用SumExpression,从程序结构上看没有什么问题,但是这段程序在执行update的时候会进行一次虚函数调用效率不高。

// virtual function call
DataBlock input_block;
Expression *expression = new SumExpression();
Context context;
for(int i = 0;i < input_block.size();++i) {
    expression->update(&context, input_block[i]); // update
}

我们再编写一段非虚函数调用,做一个benchmark

class SumExpressionNoVirtualCall {
public:
	void update(Context* context,DataType &input) {
		(*(DataType*)context) += input;
	}
};

测试代码和测试结果在这里: https://quick-bench.com/q/LOSGHOhXu_n5Y7FpEmQ7PyLKvWw

其中虚函数调用比非虚函数调用的版本满了大约49倍,虚函数调用版本主要的cpu开销在函数调用以及调用前的寄存器状态保存,而非虚函数通过内联优化消除了函数调用。

我们希望SumExpress跑的更快,因此第二个版本是把基类强制转为超类,再进行函数调用

// 改动 1
for(int i = 0;i < block.size();++i) {
    ((SumExpression*)(expression))->update(&context, block[i]); // update
}

但是改动1并不会有任何的性能提升。原因是SumExpression中的update函数仍然是一个虚函数,编译器并不能确定是否有其他类可能会继承SumExpression重写了update函数,所以编译器不敢轻易的展开。所以我们需要给这个函数加一个关键字final

class SumExpressionV2 final: public Expression

重新测试: https://quick-bench.com/q/rzKzsSTRTzDJK7BIakYAu_4Au-c
显然已经和非内联函数性能一致,汇编中也找不到call的痕迹了。

CRTP

我们希望cast代码可以自动的帮我们生成,每次都写太费劲了,假如要写100个算子,编写代价是非常高的,于是神奇的模板就出来了。但是更神奇的是,C++支持递归模板,正好可以帮我们解决这个问题。

class IBase {
public:
    virtual void batch_update(Context* context,DataBlock &input_block) = 0;
}

template <typename T>
class Base: public IBase
{
public:
    void batch_update(Context* context,DataBlock &input_block)
    {
        T& derived = static_cast<T&>(*this);
        for(int i = 0;i < input_block.size(); ++i) {
        	derived->update(&context, input_block[i]);
        }
    }
};
// 这里递归了
class SumExpressionCRTP final: public Base<SumExpressionCRTP> {
public:
	void update(Context* context,DataType &input) {
		(*(DataType*)context) += input;
	}
};

这里虽然batch_update这个仍然是一个虚函数调用,但是调用频率已经变得非常低了。因此借助CRTP,我们可以获得一个易用性和性能都不错的一个版本。

原文地址:https://www.cnblogs.com/stdpain/p/14590681.html