ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Apache Arrow:PyArrow 与 R 零拷贝数据互操作实践——rpy2、reticulate 与 C Data Interface 全解析

Apache Arrow:PyArrow 与 R 零拷贝数据互操作实践——rpy2、reticulate 与 C Data Interface 全解析 Apache ArrowPyArrow 与 R 零拷贝数据互操作实践——rpy2、reticulate 与 C Data Interface 全解析【免费下载链接】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本篇围绕 Apache Arrow 官方文档《Integrating PyArrow with R》展开完整讲解三种 Python 与 R 之间交换 Arrow 数据的技术路线通过rpy2/rpy2-arrow从 Python 调用 R 函数、通过reticulate从 R 调用pyarrow计算函数以及绕过封装直接基于 Arrow C Data Interface 手工导出/导入ArrowArray的底层实现。读完本文你将掌握如何在两个语言运行时之间零成本传递 Arrow Array避免任何序列化/反序列化开销并能对照仓库源码理解_export_to_c、Array$import_from_c、ExportArray/ImportArray等关键调用链的底层原理。背景同进程数据交换的 C Data InterfaceArrow 支持通过 C Data Interface 在同一进程内交换数据。该机制的核心思想是各语言绑定不再互相复制缓冲区、解码布局而是通过统一的 C 结构体ArrowArray数据与ArrowSchema类型元数据移交数据的引用与所有权从而让 Python 与 R 两个运行时直接共享同一块内存。这正是本文两个语言方向互调的基础——官方文档明确说明无论是 Python 调 R 还是 R 调 Python最终都可以走这条零拷贝通道。更完整的 PyArrow 生态集成Java、Substrait、CUDA 等可参见 PyArrow Integrations 索引。前提条件原文档明确说明Python 环境已正确安装pyarrowR 环境已正确安装arrow包本仓库r/目录即为该 R 绑定源码。从 Python 调用 R 函数rpy2 基础调用先看最基础的场景一个接收 Arrow Array、给所有元素加3的 R 函数library(arrow) addthree - function(arr) { return(arr 3L) }将其保存为addthree.R文件以便复用。要在 Python 中调用任意 R 函数可使用rpy2库——它在 Python 解释器内部嵌入一个完整的 R 运行时$ pip install rpy2创建addthree.py加载addthree.R并调用其中的函数import rpy2.robjects as robjects # Load the addthree.R file r_source robjects.r[source] r_source(addthree.R) # Get a reference to the addthree function addthree robjects.r[addthree] # Invoke the function r addthree(3) # Access the returned value value r[0] print(value)运行结果验证 Python 成功访问了 R 函数$ python addthree.py 6传递 Arrow Arrayrpy2-arrow 的零拷贝转换若不想在两种语言间传递普通标量而是传递Arrow Array则需要rpy2-arrow模块它实现了rpy2对 Arrow 类型的支持$ pip install rpy2-arrowrpy2-arrow实现了 PyArrow 对象到 R Arrow 对象的双向转换器且不产生任何数据拷贝——它依赖的就是 C Data Interface。修改后的addthree.pyimport rpy2.robjects as robjects from rpy2_arrow.pyarrow_rarrow import (rarrow_to_py_array, converter as arrowconverter) from rpy2.robjects.conversion import localconverter r_source robjects.r[source] r_source(addthree.R) addthree robjects.r[addthree] import pyarrow array pyarrow.array((1, 2, 3)) # Enable rpy2-arrow converter so that R can receive the array. with localconverter(arrowconverter): r_result addthree(array) # The result of the R function will be an R Environment # we can convert the Environment back to a pyarrow Array # using the rarrow_to_py_array function py_result rarrow_to_py_array(r_result) print(RESULT, type(py_result), py_result)运行输出$ python addthree.py RESULT class pyarrow.lib.Int64Array [ 4, 5, 6 ]要点解析localconverter(arrowconverter)是rpy2的上下文管理器仅在with块内启用 PyArrow 对象与 R Arrow 对象的自动转换返回值是一个 R EnvironmentArrow 对象在 R 侧是 Environment 实现需用rarrow_to_py_array显式转换回 PyArrow Array。从 R 调用 Python 函数reticulate反向调用由 R 生态的reticulate库完成。若希望在 R 中对 R 创建的 Array 调用pyarrow.compute.add可以在 R 中直接导入pyarrow# Load arrow and reticulate libraries library(arrow) library(reticulate) # Create a new array in R a - Array$create(c(1, 2, 3)) # Make pyarrow.compute available to R pc - import(pyarrow.compute) # Invoke pyarrow.compute.add with the array and 3 # This will add 3 to all elements of the array and return a new Array result - pc$add(a, 3) # Print the result to confirm its what we expect print(result)运行输出$ R --silent -f addthree.R Array double [ 4, 5, 6 ]底层原理C Data Interface 在两种语言中的源码实现上面两条路线rpy2-arrow与reticulate在底层都使用 Arrow C Data Interface。要理解这一点可以对照仓库中的源码实现。PyArrow 侧_export_to_c与_import_from_cArray 的 Cython 实现 提供了两个低层接口Array._export_to_c(out_ptr, out_schema_ptr0)把当前 Array 导出到调用方提供的ArrowArray结构体指针若同时提供ArrowSchema指针类型会一并导出。源码注释明确警告这是面向专家的低层函数且若导出的结构体未交给消费方数组内存会泄漏——所有权随导出转移给了接收方Array._import_from_c(in_ptr, type)静态方法从ArrowArray指针导入 Arraytype可以传DataType对象也可以直接传ArrowSchema的裸指针内部经ImportArray完成导入nogil块中释放 GIL 执行。此外还存在 PyCapsule 风格的__arrow_c_array__协议见 python/pyarrow/array.pxi这是 C Data Interface 的现代化封装形式。指针运算所需的ffi由python/pyarrow/cffi.py提供依赖可选的cffi包其测试python/pyarrow/tests/test_cffi.py在cffi未安装时会跳过说明该路径是可选的专家级通道。R 侧Array$import_from_c与$export_to_cR 绑定的 Array 类在 r/R/array.R 中定义导出方法export_to_c function(array_ptr, schema_ptr) ExportArray(self, array_ptr, schema_ptr)并在 r/R/array.R#L215 挂载导入方法Array$import_from_c - ImportArray这两个 R 方法最终落到 C 桥接层 r/src/bridge.cppstd::shared_ptrarrow::Array ImportArray(arrow::r::Pointerstruct ArrowArray array, arrow::r::Pointerstruct ArrowSchema schema) { return ValueOrStop(arrow::ImportArray(array, schema)); }导出侧对应ExportArray两者都直接调用 libarrow C ABI 的arrow::ImportArray/arrow::ExportArrayR 方法到 C 的绑定入口在 r/src/arrowExports.cpp 中以_arrow_ImportArray/_arrow_ExportArray注册。一个值得注意的细节R 无法原生传递 64 位整数Python 侧拿到的ArrowArray地址是 64 位裸指针而 R 没有原生的 64 位整型。R 绑定为此实现了宽松的 Pointer 包装器r/tests/testthat/test-bridge.R 中的测试覆盖了 external pointer、double-casted pointer、integer64-casted pointer、raw 表示以及character字符串表示的指针均可被export_to_c接受。这正是下文示例中把指针转成字符串传给 R做法在 R 侧得到支持的直接证据——字符串地址在 R 侧会被转换回原始指针。进阶不依赖封装库手工走 C Data Interface如果想脱离rpy2-arrow、直接演示 C Data Interface 的完整生命周期官方文档给出了如下改造方案。R 侧包装addthree_cdata在addthree.R中增加一个包装函数负责从 C Data Interface 导入 Array再调用真正的addthreelibrary(arrow) addthree_cdata - function(array_ptr_s, schema_ptr_s) { a - Array$import_from_c(array_ptr, schema_ptr) return(addthree(a)) } addthree - function(arr) { return(arr 3L) }Python 现在通过array_ptr_s和schema_ptr_s两个参数把数组与 schema 的地址传给 RR 据此重建Array并调用addthree。Python 侧完整的导出/导入流程addthree.py需要依次完成构造 PyArrow Array → 通过 C Data Interface 导出 → 把引用传给 R 函数 → R 侧再把结果导回 C Data 结构 → Python 侧导入# Get a reference to the addthree_cdata R function import rpy2.robjects as robjects r_source robjects.r[source] r_source(addthree.R) addthree_cdata robjects.r[addthree_cdata] # Create the pyarrow array we want to pass to R import pyarrow array pyarrow.array((1, 2, 3)) # Import the pyarrow module that provides access to the C Data interface from pyarrow.cffi import ffi as arrow_c # Allocate structures where we will export the Array data # and the Array schema. They will be released when we exit the with block. with arrow_c.new(struct ArrowArray*) as c_array, \ arrow_c.new(struct ArrowSchema*) as c_schema: # Get the references to the C Data structures. c_array_ptr int(arrow_c.cast(uintptr_t, c_array)) c_schema_ptr int(arrow_c.cast(uintptr_t, c_schema)) # Export the Array and its schema to the C Data structures. array._export_to_c(c_array_ptr) array.type._export_to_c(c_schema_ptr) # Invoke the R addthree_cdata function passing the references # to the array and schema C Data structures. # Those references are passed as strings as R doesnt have # native support for 64bit integers, so the integers are # converted to their string representation for R to convert it back. r_result_array addthree_cdata(str(c_array_ptr), str(c_schema_ptr)) # r_result will be an Environment variable that contains the # arrow Array built from R as the return value of addthree. # To make it available as a Python pyarrow array we need to export # it as a C Data structure invoking the Array$export_to_c R method r_result_arrayexport_to_c, str(c_schema_ptr)) # Once the returned array is exported to a C Data infrastructure # we can import it back into pyarrow using Array._import_from_c py_array pyarrow.Array._import_from_c(c_array_ptr, c_schema_ptr) print(RESULT, py_array)逐步对照源码理解这段代码arrow_c.new(struct ArrowArray*)用 cffi 分配两个空的 C Data 结构体with块退出时自动释放——这是 Python 侧的所有权管理array._export_to_c(c_array_ptr)触发 Array._export_to_c把缓冲区引用写入ArrowArrayarray.type._export_to_c(c_schema_ptr)同理导出类型指针以字符串形式跨语言传递R 无 64 位整型见前文测试佐证R 侧Array$import_from_c执行ImportArray重建对象addthree计算完成后通过r_result_arrayexport_to_c即 r/R/array.R#L167 的ExportArray把结果写回同一对 C 结构体Python 用pyarrow.Array._import_from_c(c_array_ptr, c_schema_ptr)python/pyarrow/array.pxi#L2030从裸指针导入全程无数据拷贝。运行输出注意首行的 R 包挂载提示$ python addthree.py R[write to console]: Attaching package: arrow RESULT [ 4, 5, 6 ]实践要点与限制总结路线工具数据通道适用场景Python → R简单值rpy2R 对象编解码调用任意 R 函数、传基本类型Python → RArrow Arrayrpy2-arrowC Data Interface零拷贝传递 Array/ChunkedArray 等 Arrow 对象R → PythonreticulateC Data Interface底层在 R 中调用pyarrow计算/IO 函数Python ↔ R裸指针pyarrow._export_to_c/Array$import_from_cC Data Interface 手工管理自定义封装、调试、理解底层机制需要注意的限制与陷阱内存所有权_export_to_c的源码注释明确提醒导出的结构体必须交给消费方处理否则数组内存泄漏Python 侧with块负责释放自己分配的空结构体而数据所有权已随导出移交可选依赖pyarrow.cffi依赖cffi包未安装时相关测试被跳过见 python/pyarrow/tests/test_cffi.py使用前需确认已pip install cffi64 位整数向 R 传递指针地址时须转为字符串由 R 侧 Pointer 包装器还原r/tests/testthat/test-bridge.R 验证了字符串表示的支持同进程约束C Data Interface 是进程内机制跨进程数据交换应改用 Arrow IPC 等序列化格式。完整原文参见 Python–R 集成文档C Data Interface 的规范细节参见 C Data Interface 文档。【免费下载链接】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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进