IoTDB CSharp Client ByteBuffer实现介绍

在进行RowRecords以及Tablet的插入时,我们需要对多行RowRecord和Tablet进行序列化以进行发送。客户端中的序列化实现主要依赖于ByteBuffer完成。接下来我们介绍ByteBuffer的实现细节。本文包含如下几点内容:

  • 序列化的协议
  • C#与Java的大小端的差异
  • ByteBuffer内存倍增算法

一、序列化协议

客户端向IoTDB服务器发送的序列化数据总体应该包含两个信息。

  • 数据类型
  • 数据本身

其中对于字符串的序列化时,我们需要再加入字符串的长度信息。即一个字符串的序列化完整结果为:

[类型][长度][数据内容]

接下来我们分别介绍RowRecordTablet的序列化方式

1.1 RowRecord

我们对RowRecord进行序列化时,伪代码如下:

    public byte[] value_to_bytes(List<TSDataType> data_types, List<string> values){
        ByteBuffer buffer = new ByteBuffer(values.Count);
        for(int i = 0;i < data_types.Count(); i++){
            buffer.add_type((data_types[i]);
            buffer.add_val(values[i]);
        }
    }

对于其序列化的结果格式如下:

[数据类型1][数据1][数据类型2][数据2]...[数据类型N][数据N]

其中数据类型为自定义的Enum变量,分别如下:

public enum TSDataType{BOOLEAN, INT32, INT64, FLOAT, DOUBLE, TEXT, NONE};

1.2. Tablet序列化

使用Tabelt进行数据插入时有如下限制:

限制:Tablet中数据不能有空值

由于向 IoTDB服务器发送Tablet数据插入请求时会携带行数列数, 列数据类型,所以Tabelt序列化时我们不需要加入数据类型信息。Tablet按照列进行序列化,这是因为后端可以通过行数得知出当前列的元素个数,同时根据列类型来对数据进行解析。

CSharp与Java序列化数据时的大小端差异

由于Java序列化默认大端协议,而CSharp序列化默认得到小端序列。所以我们在CSharp中序列化数据之后,需要对数据进行反转这样后端才可以正常解析。同时当我们从后端获取到序列化的结果时(如SessionDataset),我们也需要对获得的数据进行反转以解析内容。这其中特例便是字符串的序列化,CSharp中对字符串的序列化结果为大端序,所以序列化字符串或者接收到字符串序列化结果时,不需要反转序列结果。

ByteBuffer内存倍增法

拥有数万行的Tablet的序列化结果可能有上百兆,为了能够高效的实现大Tablet的序列化,我们对ByteBuffer使用内存倍增法的策略来减少序列化过程中对于内存的申请和释放。即当当前的buffer的长度不足以放下序列化结果时,我们将当前buffer的内存至少扩增2倍。这极大的减少了内存的申请释放次数,加速了大Tablet的序列化速度。

private void extend_buffer(int space_need){
        if(write_pos + space_need >= total_length){
            total_length = max(space_need, total_length);
            byte[] new_buffer = new byte[total_length * 2];
            buffer.CopyTo(new_buffer, 0);
            buffer = new_buffer;
            total_length = 2 * total_length;
        }
}

同时在序列化Tablet时,我们首先根据Tablet的行数列数以及每一列的数据类型估计当前Tablet序列化结果所需要的内存大小,并在初始化时进行内存的申请。这进一步的减少了内存的申请释放频率。

通过上述的策略,我们在一个有20000行的Tablet上进行测试时,序列化速度相比Naive数组长度动态生长实现算法具有约35倍的性能加速。