Apache Arrow 列式格式导论:内存布局、数据类型与跨语言数据共享标准

发布时间:2026/9/14 6:32:58
Apache Arrow 列式格式导论:内存布局、数据类型与跨语言数据共享标准 Apache Arrow 列式格式导论内存布局、数据类型与跨语言数据共享标准【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrowApache Arrow 的诞生源于对表格型数据tabular data表示与跨系统交换标准的迫切需求——统一的表示标准可以降低数据序列化/反序列化的计算成本也可以降低不同编程语言实现之间系统集成的成本。本文基于仓库中 docs/source/format/Intro.rst 官方文档系统讲解 Arrow Columnar Format列式内存格式的核心概念从行式/列式存储对比、Array/Buffer 基本抽象、Null 支持到定长/变长/嵌套等各类物理布局再到字典编码、Run-End 编码、扩展类型与 IPC/C Data Interface 数据共享协议。读完后你将能够从内存布局层面理解 Arrow 的性能来源并知道在真实实现如 Arrow C 与 PyArrow中这些概念对应哪些类与缓冲区。为什么需要 Arrow从表格数据到通用内存标准Apache Arrow 的核心是围绕表格型数据的表示与交换建立一套标准。这套标准的采纳带来了两方面收益降低计算成本统一的内存表示避免了系统间反复序列化/反序列化的开销特别是零拷贝场景下收益显著降低实现成本各语言实现只需遵循同一套规范即可与其他语言实现的 Arrow 数据互通不必为每个系统对分别编写适配层。Arrow 规范可以用任何编程语言实现仓库中官方实现覆盖 Ccpp/src、Python/PyArrowpython/pyarrow、Java、R、Ruby、C GLib、MATLAB 等多个语言。一个实现通常包含两部分用该语言构造表达的格式定义以及常见的内存数据分析算法例如切片 slicing 与拼接 concatenating。不同实现的成熟度差异如算法丰富程度可参见 docs/source/status.rst 中的实现状态页。除最初的愿景外Arrow 已发展为一套多语言的内存分析处理库集合覆盖以下主题零拷贝共享内存与基于 RPC 的数据移动读写各类文件格式如 CSV、Apache ORC、Apache Parquet内存分析与查询处理。行式存储与列式存储Arrow 为何选择列式布局Arrow 聚焦于表格数据。例如一张 4 行多列的表格参见 columnar-diagram_1.svg在内存中有两种组织方式行式格式row-based数据按行存储同一行的各字段在内存中相邻参见 columnar-diagram_2.svg列式格式column-based数据按列存储同一列的所有值在内存中相邻参见 columnar-diagram_3.svg。列式组织让过滤、分组、聚合等分析操作因内存局部性memory locality而更加高效CPU 访问的内存地址彼此靠近缓存命中率更高。更重要的是连续内存布局使得计算可以被向量化vectorization——现代 CPU 大多支持 SIMD 指令单条指令同时操作多个数据可用一条 CPU 指令并行处理向量数据。Arrow 正是解决这一问题的规范它采用列式布局作为通用标准。核心抽象Array、Buffer 与 Physical Layout在 Arrow 术语中Array数组一列数据。数组可以是不同数据类型不同数据类型的值在内存中的存储方式各不相同Physical Layout物理内存布局描述这些值在内存中如何排列的规范是 Arrow 规范的核心Buffer缓冲区一段连续的内存区域用于存储数组的数据。一个数组由一个或多个 Buffer 组成。在 Arrow C 实现中三者均有直接对应物cpp/src/arrow/buffer.h 中的Buffer类注释明确其为包含指向一块具有特定大小的连续内存的指针的对象并区分size可能包含有效数据的字节数与capacity分配的总字节数且恒有Size Capacitycpp/src/arrow/array/array_base.h 中的Array基类则持有length()元素个数、offset()支持零拷贝切片的相对偏移以及null_bitmap()等接口。Intro.rst接下来介绍的各类物理布局的完整规范参见 docs/source/format/Columnar.rstArrow Columnar Format版本 1.5。下面按文档脉络逐类展开。空值支持Validity Bitmap有效性位图Arrow 对所有数据类型都支持缺失值null数组中任意值无论基本类型还是嵌套类型语义上都可能是 null。实现方式是在数据之外配备一个专用缓冲区——validity bitmap有效性位图又称 null bitmap位值为1该位置的值非空valid位值为0该位置的值是 null。该位图是可选的如果数组没有缺失值可以不分配该缓冲区如下文示例图中第 1 列所示。Array::IsValid(i)在 C 中正是通过读取该位图实现见 cpp/src/arrow/array/array_base.h当null_bitmap_data_为空时则按类型分派处理。注意位序细节由于采用最低有效位编号least-significant bit numberingArrow 规定在 8 位一组内从右向左读取 validity bitmap——文档中的示意图均按此约定绘制。基本Primitive布局定长基本布局Fixed Size Primitive Layout基本类型列中每个值具有相同的物理字节大小。采用该布局的数据类型包括有符号/无符号整数、浮点数、布尔、Decimal 以及时间temporal类型物理布局示意见 primitive-diagram.svg。两点重要特例布尔类型虽属 primitive 布局但值以位而非字节编码物理布局包含一个 values 位图缓冲区可能再加上 validity 位图缓冲区见 bool-diagram.svgNull 数据类型所有值均为 null此时不分配任何缓冲区。变长二进制与字符串Variable length binary and string与定长布局相对变长布局允许数组中每个元素具有不同的字节大小用于binary二进制与string字符串数据示意见 var-string-diagram.svg所有元素的字节连续存储在单个缓冲区或内存区域中布局额外包含整数偏移offsets缓冲区用于定位每个元素的起止offsets 缓冲区总是比数组多一个元素——最后两个偏移定义了最后一个元素的起点与终点binary 与 string 共享完全相同的物理布局唯一区别是 string 型数组假定包含合法的 UTF-8 文本。普通版与 Large 版的区别在于偏移的数据类型binary/string 使用 int32 偏移large binary/large string 使用 int64 偏移。使用 32 位偏移的数据类型每个数组最大约2GB更大数据可以继续使用非 large 变体但需要拆成多个 chunk。C 实现中对应类包括LargeStringArray、StringArray等见 cpp/src/arrow/array/array_binary.cc。变长二进制与字符串视图Variable length binary and string view该布局是经典变长 binary 布局的替代方案源自慕尼黑工业大学的 UmbraDB与 DuckDB、Velox 中的字符串布局相似有时被称为 German strings。其核心创新是views 缓冲区示意见 var-string-view-diagram.svg每条 view 包含字符串的长度小字符串字符直接内联在 view 中大字符串仅存储字符串的前 4 个字节外加指向某个数据缓冲区可能有多个的偏移量。由于通过 offsetlength 引用数据缓冲区所有元素的字节不必连续存储于单个缓冲区从而支持对变长元素进行乱序写入out-of-order writing。这对字符串处理性能至关重要前缀快路径字符串比较常在头 4 个字节内就得出结果前缀让比较有了高效的快速通道选择元素是gather操作在定宽的 views 缓冲区上直接聚集即可无需重写 values 缓冲区。C 中的BinaryViewType/StringViewType类定义于 cpp/src/arrow/type.h 与 cpp/src/arrow/type.h其 UTF-8 校验逻辑在 cpp/src/arrow/array/array_binary.cc 的StringViewArray::ValidateUTF8()中实现。嵌套Nested布局嵌套数据类型引入**父数组parent与子数组child**的概念用于表达物理值数组之间的嵌套结构关系。嵌套类型依赖一个或多个子类型——例如 List 是父类型它的一个孩子是列表中元素的类型。List 与 Fixed Size ListList列表每个元素是同一数据类型的序列。布局与变长 binary/string 类似有一个 offsets 缓冲区定义每个元素的起止所有值连续存储在 values 子数组中见 var-list-diagram.svg。list 使用 int32 偏移large list 使用 int64 偏移Fixed Size List定长列表变长 list 的特例每个槽位包含固定大小的序列所有列表等长因此不再需要 offsets 缓冲区见 fixed-list-diagram.svg。List View与 list 不同list view在 offsets 缓冲区之外额外增加一个 size 缓冲区见 var-list-view-diagram.svgoffsets 仍指示每个元素的起点但 size 单独保存元素大小不再由相邻 offsets 推导从而支持乱序偏移。Structstruct 是嵌套类型由有序的字段序列每个字段含数据类型与名称参数化每个字段对应一个子数组子数组相互独立内存中不必相邻只需等长。可以把单个 struct 字段视为键值对键字段名保存在 schema 中值保存在子数组中。由于子数组相互独立Arrow不强制struct 的 validity bitmap 与其子数组的物理一致性逻辑上struct 行只有当父位图与子位图在该槽位都为 1 时才有效逻辑 AND。这也允许在 struct 为 null 的位置子数组中存在隐藏数据如示例中的alice见 struct-diagram.svg。MapMap 表示每个值为可变数量键值对的嵌套数据其物理表示与{key, value}结构体列表相同。与 struct 的区别在于struct键保存在 schema 中要求键必须是字符串值按字段分别存于多个子数组map只有一个子数组保存所有键因此键必须同数据类型但不一定是字符串另一个子数组保存所有值值也需同数据类型但不要求与键类型一致。此外 map 把 struct 包在 list 中因 list 形状可变需要 offsets 缓冲区见 map-diagram.svg。Union联合union 是嵌套类型每个槽位的值从一组候选 Arrow 数据类型中选取即混合类型数组。与其它类型不同union没有自己的 validity bitmap空值由子数组决定。Arrow 定义两种 unionDense Union稠密联合每个出现的类型对应一个子数组外加两个自有缓冲区见 dense-union-diagram.svgTypes 缓冲区每个槽位保存数据类型 id。id 通常就是子数组的索引但 id 与子索引的对应关系是类型的参数Offsets 缓冲区每个槽位保存其在对应子数组中的相对偏移。Sparse Union稀疏联合结构与 dense union 相同但省略 offsets 缓冲区此时各子数组长度都与 union 等长见 sparse-union-diagram.svg。C 中Array::IsNull对两种 union 分别走IsNullSparseUnion/IsNullDenseUnion逻辑见 cpp/src/arrow/array/array_base.h。字典编码布局Dictionary Encoded Layout当数据包含大量重复值时字典编码非常有效值用引用字典通常由唯一值组成的整数表示见 dictionary-diagram.svg。该布局在类别型数据、去重与压缩场景中广泛使用。Run-End 编码布局Run-End Encoded LayoutRun-end encoding 非常适合表示包含相同值连续序列run的数据。一个 run-end encoded 数组没有自己的缓冲区而是拥有两个子数组见 ree-diagram.svgRun ends 数组保存每个 run 结束处的数组索引run ends 的数量等于父数组的长度Values 数组去重后的实际值连同 null 一起。需要注意父数组的 null严格在 values 数组中表示。另外run-end 布局是 Columnar 格式中唯一不保证随机访问 O(1) 的布局为 O(log n)。C 中对应类型为 cpp/src/arrow/type.h 的RunEndEncodedType其 Flatbuffers 定义位于 format/Schema.fbs。Arrow 术语总览Terminology文档给出了一组贯穿各实现的标准术语定义术语定义Physical layout关于数组值如何在内存中表示的规范。Buffer具有给定字节长度的连续内存区域用于存储数组数据只有知道包裹该缓冲区的数组数据类型时才能谈缓冲区中的元素个数。Array已知长度、一维、连续、所有值同类型的值序列由零个或多个缓冲区组成。Chunked Array已知长度、一维、不连续、所有值同类型的值序列由零个或多个数组chunks组成。注这是特定实现如 Arrow C、PyArrow的概念。RecordBatch连续、二维的数据结构由一组等长的有序数组组成。Schema有序字段集合传达 RecordBatch 或 Table 等对象的所有数据类型可含可选的键值元数据。Field包含字段名、数据类型、可空标志以及可选键值元数据描述 RecordBatch 中某一列。Table不连续、二维的数据块由一组有序的 Chunked Array 组成各 Chunked Array 等长但类型可不同不同列可以有不同的分块方式。注Table 是特定实现如 Arrow C、PyArrow的概念例如 Java 实现中 Table 不是 Chunked Array 的集合而是 RecordBatch 的集合。Table 与 RecordBatch 的结构差异可参考仓库中 tables-versus-record-batches.svg 示意图。上述概念在 C 实现中分别对应 Array、ChunkedArray、RecordBatch、Table、Buffer 等类。更多术语见 docs/source/format/Glossary.rst。扩展类型Extension Types当系统或应用需要为标准 Arrow 数据类型附加自定义语义时可以通过定义扩展类型实现。扩展类型的做法是为任意内置 Arrow 数据类型即存储类型 storage type注解自定义类型名与可选的序列化表示具体通过在 Field 元数据中设置ARROW:extension:name与ARROW:extension:metadata两个键完成。典型示例包括 UUID 扩展类型与 FixedShapeTensor 扩展类型。扩展类型分两类Canonical Extension Types规范扩展类型在 Arrow 内部定义目的是让广为人知的扩展类型定义可共享提升不同系统间集成 Arrow 列式数据的互操作性Community Extension Types社区扩展类型在特定领域内已确立为标准的 Arrow 扩展类型例如 GeoArrow——一套用于表示矢量几何图形的 Arrow 扩展类型集合。详细规范参见 docs/source/format/Metadata.rst 与 docs/source/format/CanonicalExtensions.rst。共享 Arrow 数据IPC 与 C Data InterfaceArrow 内存布局是与具体实现无关的通用表格数据内存表示标准。为在应用间进行无歧义的通信Arrow 标准定义了两类协议跨进程或跨网络共享数据使用IPC 消息格式见 docs/source/format/IPC.rst。该规范定义了如何把 Arrow 数组或 record batch 的缓冲区堆叠起来进行序列化与反序列化同一进程内共享数据使用C Data Interface见 docs/source/format/CDataInterface.rst。它用于在同一进程内的不同库之间以零拷贝方式共享同一块缓冲区。此外还有面向设备GPU 等内存共享的 CDeviceDataInterface.rst、面向流式传输的 CStreamInterface.rst 以及 RPC 场景的 Flight.rst 等配套规范共同构成 Arrow 跨语言、跨进程的数据交换体系。小结Apache Arrow 通过一套语言无关的列式内存布局规范为跨系统表格数据交换提供了统一底座validity bitmap 统一表达空值定长/变长/嵌套/字典/Run-End 等物理布局分别针对不同数据形态给出内存效率最优的表示而 IPC 与 C Data Interface 则打通了进程内外、网络间的零拷贝共享。理解这些布局是深入使用 Arrow C/PyArrow 进行高性能内存分析或基于规范自行实现 Arrow 的起点。【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考