Apache Arrow
https://www.kdnuggets.com/2017/02/apache-arrow-parquet-columnar-data.html
https://arrow.apache.org/
https://hyper-db.de/index.html#team
CMU-db,https://github.com/cmu-db/terrier
FlatBuffers
https://halfrost.com/flatbuffers_schema/
FlatBuffers 的主要目标是避免反序列化,数据的传输和读取都是可以直接用二进制形式
而Json需要,从对象序列化到string,传输,使用时再反序列化到对象
FlatBuffers (9490 star) 和 Cap'n Proto (5527 star)、simple-binary-encoding (1351 star) 一样,它支持“零拷贝”反序列化,在序列化过程中没有临时对象产生,没有额外的内存分配,访问序列化数据也不需要先将其复制到内存的单独部分,这使得以这些格式访问数据比需要格式的数据(如JSON,CSV 和 protobuf)快得多。
FlatBuffers 专注于移动硬件(内存大小和内存带宽比桌面端硬件更受限制),以及具有最高性能需求的应用程序:游戏
SIMD
SIMD,https://blog.csdn.net/tercel_zhang/article/details/80694573
single instruction multiple data,
128bit的寄存器,可以同时放4个32bit的float,对4个float同时执行指令,比如add,就实现了SIMD
SSE指令,Streaming SIMD Extensions的缩写
SSE有8个128位寄存器,XMM0 ~XMM7
可以进行,packed或scalar计算
Arrow Format
arrow 格式的特性,
-
Data adjacency for sequential access (scans)
-
O(1) (constant-time) random access
-
SIMD and vectorization-friendly
-
Relocatable without “pointer swizzling”, allowing for true zero-copy access in shared memory
pointer swizzling,指在序列化的时候,需要对point做处理,因为直接存下去是没意义的,下次反序列化的时候,地址变了
要理解Arrow的format,直接看例子,
因为是列存,所以存储的格式一定是数组形式
Fixed-size Primitive
两个需要注意的,
1. 用validity bitmap来表示每个item是否为null,如果null count=0,可以不存这bitmap
We use least-significant bit (LSB) numbering (also known as bit-endianness). This means that within a group of 8 bits, we read right-to-left
你只需要知道这个bitmap是从右到左的
2. Arrow需要做Padding,这个也是自然的,对cache和SIMD比较友好
The recommendation for 64 byte alignment comes from the Intel performance guide that recommends alignment of memory to match SIMD register width. The specific padding length was chosen because it matches the largest SIMD instruction registers available on widely deployed x86 architecture (Intel AVX-512).
看下定长的原始类型,
由于定长,偏移量可以直接算出来,所以不用记录
null也占用同样大小的空间,这里因为要Padding,所以一个定长的数组,至少要占用128字节
Variable-size List Layout
如果每一个列是个变长的list,
首先在validity的时候,[]是非null;所以只有一个null
Value array在存储数据的时候都是拉平的, 把每个list里面的item依次存下去,这里对于list的null和[]是相同的处理,就是ignore;
然后会单独存储一个offset buffer,记录每个list在Value array中的起始offset
这里有意思的是,如果是list嵌套list,如何表示?
做法就是每多一层list嵌套,就增加一层offsets buffer来记录,
比如例子中,第一个offset buffer记录外层list是由哪些内层list组成
这样在读取数据的时候,可以直接基于这个结构,通过多次offset跳转,不用反序列化,实现zero copy?
Struct Layout
arrow还支持struct,结构体
比如,
Struct <
name: VarBinary
age: Int32
>
对于结构体的支持,就比较复杂了,因为结构体中每个field是不同类型的,如果要发挥列存或向量化的优势,必须要把每个field独立存储
所以产生child array的概念,每个field对应于一个child array
下面的例子,首先用一个bitmap表示struct本身是否为null
然后在children arrays里面分两个array分别存储,
第一个field是string变长的所以要offset buffer,第二个int是定长的
Union Layout
首先什么是union?和struct的区别,就是struct定义的field同时出现,Union是只出现一个
所以直觉上,我们可以用和struct类似的方式存储Union
先用children arrays来存储每个field,然后只需要一个全局的offset buffer,因为一个slot只会出现一个field,再用一个Type buffer来记录每个slot选的是哪个field
这种记录方式,就是Dense Union
这里有个差异,Union不会直接用bitmap记录null,而是通过child array去记录,这样需要多读几跳,为了省空间?
还有一种记录方式,Sparse Union
之所以叫Sparse,因为在每个child array中都会记录全局的bitmap,每个没有选中当前field的位置也都需要占空间
这样不需要全局的offset buffer,但是多占用很多空间,感觉很废的样子
好处,
While a sparse union may use significantly more space compared with a dense union, it has some advantages that may be desirable in certain use cases:
-
A sparse union is more amenable to vectorized expression evaluation in some use cases.
-
Equal-length arrays can be interpreted as a union by only defining the types array.
例子,
Java接口
Java以ValueVector为主,一个Vector代表一个列
VectorSchemaRoot
一个带schema的RecordBatch的封装
可以通过unload和load,对RecordBatch进行转换,