zoukankan      html  css  js  c++  java
  • 谈谈 内联 虚函数 和 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,我们可以获得一个易用性和性能都不错的一个版本。

  • 相关阅读:
    CentOS Linux 7 安装教程
    计算机网络概念
    计算机操作系统概念简介
    基础-计算机及操作系统和应用程序的概念
    Spring mvc注解说明
    kaptcha Spring 整合
    Spring常用注解
    Spring bean的自动装配属性
    HQL(Hibernate Query language)语言
    JDBC、Hibernate、Java类型对照表
  • 原文地址:https://www.cnblogs.com/stdpain/p/14590681.html
Copyright © 2011-2022 走看看