介绍 Apache Arrow C 数据接口


发布 2020 年 5 月 3 日
作者 Antoine Pitrou (apitrou)

Apache Arrow 包含一个跨语言、平台独立的内存列式格式,允许异构运行时和应用程序之间进行零拷贝数据共享和传输。

使用 Arrow 列式格式最简单的方法一直是依赖 Apache Arrow 社区开发的具体实现之一。该项目代码库目前包含 11 种不同编程语言的库,未来可能会增加更多语言。

然而,有些项目可能希望导入和导出 Arrow 列式格式,而无需引入新的库依赖,例如 Arrow C++ 库。因此,我们设计了一种替代方案,它在 C 级别交换数据,符合简单的数据定义。C 数据接口除了使用它的二进制文件之间共享的 C ABI 之外,不带任何依赖。C ABI 是平台范围的标准,所有生成二进制文件的编译器都必须遵守,并且极其稳定,确保了库和可执行二进制文件的可移植性。利用 C 数据接口定义的 C 结构体的两个库可以在运行时进行零拷贝数据传输,而无需任何构建时或链接时依赖要求。

了解 C 数据接口的最佳方式是阅读规范。不过,我们将快速介绍其优点。

两个简单的结构体定义

要通过 C 或 C++ 级别与 C 数据接口交互,您只需在代码中包含两个结构体类型声明(以及几个用于常量值的 #define)。这些声明仅依赖于标准 C 类型,可以直接粘贴到头文件中。只要提供外部函数接口层,其他语言也可以参与;大多数现代语言都是这种情况,例如 Python(使用 ctypescffi)、Julia、Rust、Go 等。

零拷贝数据共享

C 数据接口通过内存指针传递 Arrow 数据缓冲区。因此,根据其设计,它允许您在不复制数据的情况下将数据从一个运行时共享到另一个运行时。由于数据采用标准Arrow 内存格式,因此其布局明确且无歧义。

此设计还将 C 数据接口限制为 进程内 数据共享。对于进程间通信,我们建议使用 Arrow IPC 格式

减少封送处理

C 数据接口与在 C 或 C++ 中表达类 Arrow 数据的自然方式非常接近。只有两个方面涉及非平凡的封送处理

  • 数据类型的编码,使用非常简单的基于字符串的语言
  • 可选元数据的编码,使用非常简单的长度前缀格式

独立类型和数据表示

对于生成同一数据类型(例如,作为记录批处理流)的许多数据实例的应用程序,重复从其字符串编码重建数据类型将导致不必要的开销。为了解决此用例,C 数据接口定义了两个独立的结构:一个表示数据类型(和可选元数据),一个表示一块数据。

生命周期处理

异构运行时之间数据共享的一个常见难题是正确处理数据的生命周期。C 数据接口允许生产者通过释放回调定义自己的内存管理方案。这是一个简单的函数指针,当消费者使用完数据后将调用它。例如,当用作生产者时,Arrow C++ 库传递一个释放回调,该回调只是递减 shared_ptr 的引用计数。

应用:在 R 和 Python 之间传递数据

R 和 Python Arrow 库都基于 Arrow C++ 库,但它们各自的工具链(由 R 和 Python 打包标准强制规定)是 ABI 不兼容的。因此,无法在 R 和 Python 绑定之间直接在 C++ 级别传递数据。

使用 C 数据接口,我们规避了这一限制,并在 R 和 Python 之间提供了一个零拷贝数据共享 API。它基于 R reticulate 库。

这是一个混合 R 和 Python 库调用的会话示例

library(arrow)
library(reticulate)
use_virtualenv("arrow")
pa <- import("pyarrow")

# Create an array in PyArrow
a <- pa$array(c(1, 2, 3))
a

## Array
## <double>
## [
##   1,
##   2,
##   3
## ]

# Apply R methods on the PyArrow-created array:
a[a > 1]

## Array
## <double>
## [
##   2,
##   3
## ]

# Create an array in R and pass it to PyArrow
b <- Array$create(c(5, 6, 7))
a_and_b <- pa$concat_arrays(r_to_py(list(a, b)))
a_and_b

## Array
## <double>
## [
##   1,
##   2,
##   3,
##   5,
##   6,
##   7
## ]