介绍 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
## ]