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 内存格式,因此其布局定义明确且 unambiguous。

这种设计还将 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
## ]