clickhouse php,ClickHouse源码笔记1:聚合函数的实现

由于工作的需求,后续笔者工作需要和开源的OLAP数据库ClickHouse打交道。ClickHouse是Yandex在2016年6月15日开源了一个分析型数据库,以强悍的单机处理能力被称道。

笔者在实际测试ClickHouse和阅读ClickHouse的源码过程之中,对"战斗民族"开发的数据库十分欣赏。ClickHouse不仅是一个很好的数据库学习材料,而且同时应用了大量的CPP17的新特性进行开发,也是一个大型的Modern CPP的教导资料。

笔者接下来会陆续将阅读ClickHouse的部分心得体会与通过源码阅读笔记的方式和大家分享,坦白说,这种源码阅读笔记很难写啊。(多一分繁琐,少一分就模糊了~~)

第一篇文章,我们就从聚合函数的实现开始聊起~~ 上车!

1.基础知识的梳理

什么是聚合函数?

聚合函数: 顾名思义就是对一组数据执行聚合计算并返回结果的函数。

这类函数在数据库之中很常见,如:count, max, min, sum等等。

ClickHouse的实现接口

IAggregateFunction接口

在ClickHouse之中,定义了一个统一的聚合函数接口:IAggregateFunction.(在ClickHouse之中,所有的接口类都是以大写的I开头的。) 上文笔者提到的聚合函数,则都是作为抽象类IAggregateFunction的子类实现的。其中该接口最为核心的方法是下面这5个方法:add函数:最为核心的调用接口,将对应AggregateDataPtr指针之中数据取出,与列columns中的第row_num的数据进行对应的聚合计算。(这里可以看到ClickHouse是一个纯粹的列式存储数据库,所有的操作都是基于列的数据结构。)merge函数:将两个聚合结果进行合并的函数,通常用在并发执行聚合函数的过程之中,需要将对应的聚合结果进行合并。serialize函数与deserialize函数:序列化与反序列化的函数,通常用于spill to disk或分布式场景需要保存或传输中间结果的。addBatch函数:这是函数也是非常重要的,虽然它仅仅实现了一个for循环调用add函数。它通过这样的方式来减少虚函数的调用次数,并且增加了编译器内联的概率。(虚函数的调用需要一次访存指令,一次查表,最终才能定位到需要调用的函数上,这在传统的火山模型的实现上会带来极大的CPU开销。)  /** Adds a value into aggregation data on which place points to.

*  columns points to columns containing arguments of aggregation function.

*  row_num is number of row which should be added.

*  Additional parameter arena should be used instead of standard memory allocator if the addition requires memory allocation.

*/

virtual void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena * arena) const = 0;    /// Merges state (on which place points to) with other state of current aggregation function.

virtual void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs, Arena * arena) const = 0;    /// Serializes state (to transmit it over the network, for example).

virtual void serialize(ConstAggregateDataPtr place, WriteBuffer & buf) const = 0;    /// Deserializes state. This function is called only for empty (just created) states.

virtual void deserialize(AggregateDataPtr place, ReadBuffer & buf, Arena * arena) const = 0;    // /** Contains a loop with calls to "add" function. You can collect arguments into array "places"

*  and do a single call to "addBatch" for devirtualization and inlining.

*/    virtual void addBatch(size_t batch_size, AggregateDataPtr * places, size_t place_offset, const IColumn ** columns, Arena * arena) const = 0;

抽象类IColumn

上面的接口IAggregateFunction的函数使用到了ClickHouse的核心接口IColumn类,这里也进行简要的介绍。 IColumn 接口表达了所有数据在ClickHouse之中的用内存表达的数据结构,其他带有具体数据类型的如ColumnUInt8、ColumnArray 等, 都实现了对应的列接口,并且在子类之中具象实现了不同的内存布局。

IColumn的子类实现细节很琐碎,笔者这里就暂时不展开讲了,笔者这里就简单讲讲涉及到聚合函数调用部分的IColumn接口的对应方法:

这里columns是一个二维数组,通过columns[0]可以取到第一列。(这里只有涉及到一列,为什么columns是二维数组呢?因为处理array等列的时候,也是通过对应的接口,而array就需要应用二维数组了. )

注意这里有一个强制的类型转换,column已经转换为ColVecType类型了,这是模板派生出IColumn的子类。

然后通过IColumn子类实现的getData方法获取对应row_num行的数据进行add函数调用就完成了一次聚合函数的计算了。    void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena *) const override    {        const auto & column = static_cast(*columns[0]);        this->data(place).add(column.getData()[row_num]);

}

IAggregateFunctionHelper接口

这个接口是上面提到 IAggregateFunction的辅助子类接口,它很巧妙的通过模板的类型派生,将虚函数的调用转换为函数指针的调用,这个在实际聚合函数的实现过程之中能够大大提高计算的效率。

函数addFree就实现了我上述所说的过程,但是它是一个private的函数,所以通常我们都是通过getAddressOfAddFunction获取对应的函数地址。这在聚合查询的过程之中能够提高20%左右的执行效率。

你可能感兴趣的:(clickhouse,php)