静态代码块不执行_一个避免静态代码爆炸的C++ idiom,用于OLAP向量化执行引擎的开发...

在开发OLAP向量化执行引擎时,面临大量相似函数的编写和维护问题。文章讨论如何在C++17标准下,借鉴Haskell的guard机制,利用TypeGuard和ValueGuard减少代码冗余,实现类型安全和代码灵活性。通过示例解释了如何处理特定类型的操作,如float和double的模运算,并提供了实现和Demo。
摘要由CSDN通过智能技术生成

在实现向量执行引擎的过程中,有大量不同类型,语义相同, 代码结构很相似的函数, 比如:

  • +, - , *, /, %等算数运算.
  • 类型之间相互cast的函数.
  • 各种builtin函数.

这种函数,经过不同参数的排列组合下来, 数量非常庞大. 编写这么规模庞大函数,需要投入很大的精力. 同时, 后期维护成本也很大, 比如:

  • 增加一个新类型,比如增加Array,Map, Struct等类型。
  • 重构某一个类型,比如Decimal128的基础上,支持Decimal64, Decimal32, Decimal256.
  • 发现为某一种具体运算的更有的向量实现,需要变更算法. 比如,double转string的函数需要变更,而tinyint,smallint,int等类型的转string函数保持不变.

怎么处理这么一大堆函数,满足下面条件:

  • 代码类型安全: 如果类型错误,编译时的静态检查,可以给出报错提示.
  • 代码的boilerplate要少: 重复的代码不利于维护,或者维护时,容易漏写.
  • 代码的局部可调整性要好:代码足够的灵活,随时可以抽取一个特例,进行改写.

总之, 代码的逻辑上,要能够做到incremental refine.

本文作者起先尝试,使用模板重构DorisDB(incubator-doris的商业化版本), 但操作的过程中发现,直接使用模板,还是无法降低这种冗余. 由此想到了函数式编程语Haskell的模式匹配中有guard的机制,guard可以进一步帮助函数做更加精细的匹配. C++20里面有了Concept能够实现类似的功能, 但是C++20的concept概念,笔者多次尝试看文档,任然感觉concept语法构造不够直观,况且目前还没有广泛采用, 并且我们的c++标准目前还停留在c++17标准. 所以,我们探讨c++17的前提下,怎么实现guard机制.

为了双面guard机制的必要性, 举个例子:

比如,你要实现 float,double,tinyint,smallint,int,bigint, largeint, decimal32,decimal64,decimal128这么多类型的 add,sub,mul,div,mod,bitand,bitor,bitnot,bitxor等等函数

这些函数大同小异, 可以用一个模板的来实现, 可以了一个通用的版本, 直接用+,-, *,/, &, |, ^这些算符 ,因为很多类型已经做了重载.

struct AddOp{};
struct SubOp{};
struct MulOp{};
struct DivOp{};
struct ModOp{};

...

template<typename Op, typename Type>
struct BinaryArithmeticOperator{
    Type apply(Type const& lhs, Type const& rhs);
}

但是float和double的取模运算没法直接用%,而是用gcc的builtin函数fmod,你需要添加一个模板的特例:

你怎么添加呢?

template<> BinaryArithmeticOperator<ModOp,  float> {...}
template<> BinaryArithmeticOperator<ModOp,  double> {...}

这两个模板只是一个参数不一样,其他完全一样, 代码重复冗余;能否合并成一个一个模板特例呢?

template<typename T> BinaryArithmeticOperator<ModOp,  T> {...}

显然这样做的话, 其他类型也会匹配到这个模板,这符合预期. 因此需要一个guard机制, guard是一个类型表达式,只能接受float和double;其他类型直接fail掉,根据sfinae原则,fail掉之后,其他类型依然可以尝试匹配其他模板特例.

template<T> BinaryArithmeticOperator<ModOp,  T>  Guard(T is float or double){...}

具体在实现过过程中, 我们将上面函数写成下面形式:

struct AddOp{};
struct SubOp{};
struct MulOp{};
struct DivOp{};
struct ModOp{};

...

template<typename Op, typename Type, typename=guard::Guard, typename=guard::Guard>
struct BinaryArithmeticOperator{
    Type apply(Type const& lhs, Type const& rhs);
}

template <typename T> using ModOpGuard = guard::TypeGuard<T, ModOp>;
template <typename T> using FloatTypeGuard = guard::TypeGuard<T, float, double>;
template<typename Op, typename Type>
struct BinaryArithmeticOperator<Op, Type, ModOpGuard<Op>, FloatTypeGuard<Type>>{
    Type apply(Type const& lhs, Type const& rhs);
}

通过上述方法,可以将float和double的mod实现用一个模板特例实现.

假如,后续实现decimal32,decimal64, decimal128的mod运算,我们只要添加:

template <typename T> using DecimalTypeGuard = guard::TypeGuard<T, Decimal32, Decimal64, Decimal128>;
template<typename Op, typename Type>
struct BinaryArithmeticOperator<Op, Type, ModOpGuard<Op>, FloatTypeGuard<Type>>{
    Type apply(Type const& lhs, Type const& rhs);
}

假如后续添加decimal256呢? 也不能实现.

在具体实现过程中,还需要为non-type template parameter 提供ValueGuard.

关于TypeGuard和ValueGuard的实现和Demo,请参考:

实现:

https://github.com/satanson/cpp_etudes/blob/master/include/guard.hh​github.com

demo:

https://github.com/satanson/cpp_etudes/blob/master/unittest/test_guard.cc​github.com

上述代码,在g++ 10.2.0和clang++ 11.0.0上执行OK,行为正确.


额外需要了解我们工作,或者对我们工作感兴趣的,年末打算换工作搞数据库的同学,我们可以私信沟通.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值