编码
第一章先来看一下Rocksdb的编码情况,具体的实现在:util/coding.cc, util/coding.h, util/coding_lean.h
。Rocksdb的编码实现与leveldb基本一致的,由于需要将Key、Value等数据按序写入到内存中,并最终flush到磁盘上,因此需要一个高效的编解码实现。这里的编解码很多会用在key
size、value size的整型编码上面。
Rocksdb的整型编码方式很简单,主要支持两种方案:定长编码和变长编码。
- 定长编码:定长编码的实现比较简单,比如可以直接将4字节/8字节的整型直接按序写入到指定的位置;
- 变长编码:节省空间,如果用定长编码的方式,实际上对于小数来说,很多位其实是没必要存储。结合ASCII码的特点,所有字符ASCII码的最高位都是0,可以利用最高位去做一些特别的标记;
实现
字节端序
关于编码,首先需要了解计算机在存储字节时分为大端字节序和小端字节序两种,Rocksdb是用小端序存储(低位放在较小的地址处,高位放在较大的地址处)的,因此需要提供根据不同平台对内存存储模型进行转换的选项。
在port/port.h文件内包含了一些平台相关的的头文件:
1 |
以port_posix.h
为例,这里包含了posix平台关于大小端的定义:
1 |
|
定长编码
定长编码比较简单,首先判断大小端序,对于小端,value本身就是按小端排放的,可以直接拷贝;对于大端,则是将value的最低位放置在内存的最低地址端。
1 | inline void EncodeFixed32(char* buf, uint32_t value) { |
解码也一样,Rocksdb提供了三种整型编解码:uint16_t,uint32_t,uint64_t
。解码时,gcc会优化里面的memcpy的操作,变成inline
copy loops,提高效率。
1 | inline uint32_t DecodeFixed32(const char* ptr) { |
变长编码
前面提到的,为了节省空间,Rocksdb使用变长的编码方式varint。Rocksdb使用最高位来表示编码是否结束,而低7bit则存储实际的数据。根据统计来说,小整型出现的概率更高,这样就能节省更多的空间,比如0-127的整数都可以只用一个字节来表示,而uint32较大的数字则需要5个字节。
1 | 0001 0001 ====>> 表示33 |
具体的编码以uint32_t
为例,将uint32_t
编码成变长字符:
1 | char* EncodeVarint32(char* dst, uint32_t v) { |
uint64_t
的变长编码实现,作者用了循环来编码,每7bit一组,并在最低位判断是否需要置位1。因此对于64位整型,最多需要10个字节:
1 | inline char* EncodeVarint64(char* dst, uint64_t v) { |
解码的实现也有一些优化,主要是利用内联函数提高效率,当数字小于等于127时,直接返回结果:
1 | inline const char* GetVarint32Ptr(const char* p, |
总结
编解码这里还是比较简单和高效的,比较有意思的是实现了一个variant编码,源码值得一看。