基于 Apache Arrow 的 MATLAB 接口设计指南:从 arrow.* 包到跨语言零拷贝内存共享
【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址: https://gitcode.com/gh_mirrors/arrow13/arrow
Apache Arrow 是一个面向加速数据交换与内存分析的跨语言工具箱,其 MATLAB 接口(MATLAB Interface for Apache Arrow)旨在让 MATLAB 用户直接创建、访问与释放 Arrow 内存,并通过 Arrow 生态的列式内存格式与文件格式(Feather、Parquet、IPC)同 Python、R、Rust 等语言高效互通。本文以仓库中的设计文档 matlab/doc/matlab_interface_for_apache_arrow_design.md 为主线,结合matlab/目录下的真实源码,完整讲解其设计动机、API 骨架、三大核心用例(UC1/UC2/UC3)的落地路径,以及测试、文档与安装规划,帮助读者掌握该接口的设计全貌与工程实现细节。
设计背景与目标
设计文档将 Apache Arrow 的列式分析能力定位为构建 MATLAB 接口的出发点,并明确聚焦三个为未来更高级用例奠基的核心场景:
- UC1:使用 MATLAB 代码创建、访问和删除 Arrow 内存。
- UC2:使用 MATLAB 代码将 Arrow 内存序列化/反序列化到 Parquet、Feather、JSON、CSV 等文件格式。
- UC3:将以 MATLAB
table表示的内存表格数据以最小开销(理想情况下为零拷贝)迁移到 Python、R、Rust 等其他语言。
这三个用例共同决定了接口的形态:既要有面向普通用户的数组与表格对象模型,也要有面向文件读写的 IO 能力,还要有面向跨语言内存共享的 C 级互操作层。
总体设计:arrow.*包与 C++ 包装层
设计文档设想了一套以arrow.*命名的包化类与函数,使用户能够通过 MATLAB 代码与 Arrow C++ 库的核心功能交互。这一设计在当前仓库中已经落地为matlab/src/matlab/+arrow/目录下的真实实现(MATLAB 中+前缀目录即对应包命名空间)。
MATLAB API 骨架
文档列出的 MATLAB API 与仓库实际实现的对应关系如下:
| 设计文档中的 API | 仓库中的实际路径 |
|---|---|
arrow.Buffer | matlab/src/matlab/+arrow/+buffer/Buffer.m |
arrow.Array | matlab/src/matlab/+arrow/+array/Array.m |
arrow.RecordBatch | matlab/src/matlab/+arrow/+tabular/RecordBatch.m |
arrow.Table | matlab/src/matlab/+arrow/+tabular/Table.m |
arrow.Field/arrow.Schema | matlab/src/matlab/+arrow/+type/Field.m、matlab/src/matlab/+arrow/+tabular/Schema.m |
arrow.type.DataType及子类 | matlab/src/matlab/+arrow/+type/Type.m 及其下Float64Type.m、StringType.m、Date32Type.m、Time32Type.m、TimestampType.m等 |
arrow.memory.* | 对应 C++ 侧内存分配,MATLAB 侧以arrow.buffer.Buffer的fromMATLAB静态工厂等方式呈现 |
从实现看,arrow.type包下提供了完整的数据类型体系,包括BooleanType、NumericType(Float32/64、Int8/16/32/64、UInt8/16/32/64)、StringType、ListType、StructType、TemporalType(Date32/64、Time32/64、Timestamp),以及配套的traits目录,用于把类型 ID 映射到对应的数组代理类与构造器——这正对应文档中"返回类型特定的、arrow.Array抽象类的具体子类"的描述。
C++ API 设计
为了让 MATLAB 与 Arrow C++ 库交互,设计文档提出需要一组用于在 MATLABmxArray与 Arrow C++ 类型之间"包装/解包"(wrap/unwrap)的 C++ API,例如:
arrow::matlab::is_array/is_record_batch/is_tablearrow::matlab::unwrap_array/wrap_arrayarrow::matlab::unwrap_record_batch/wrap_record_batcharrow::matlab::unwrap_table/wrap_table
需要说明的是,从当前仓库的 C++ 源码结构看(matlab/src/cpp/arrow/matlab/),最终落地时采用了一种代理(proxy)模式而非文档示例中的unwrap/wrap命名:C++ 侧按功能域组织为array/proxy/、tabular/proxy/(含table.cc、record_batch.cc、schema.cc)、buffer/proxy/、type/proxy/、io/feather/proxy/、io/ipc/proxy/、io/csv/proxy/、c/proxy/等模块,并通过mex/gateway.cc与proxy/factory.cc统一注册;MATLAB 侧则通过libmexclass.proxy.Proxy持有对应 C++ 代理的 ID 来调用底层能力。文档中的unwrap/wrap是设计阶段的功能示意,其"把 MATLAB 对象解析为 Arrow C++ 对象"的语义在 proxy 实现中得到等价承接。
用例详解一(UC1):用 MATLAB 代码创建与操作 Arrow 内存
文档描述了 UC1 的核心场景:MATLAB 开发者可以用"普通"的 MATLAB 数组(例如double类型的数值行向量)创建arrow.Array,然后对其进行索引/切片、获取类型、从工作区清除等操作。arrow.array工厂函数会根据输入数组的 MATLAB 类型,返回arrow.Array抽象类的类型特定具体子类。
arrow.array工厂函数的分发逻辑
仓库中的 matlab/src/matlab/+arrow/array.m 完整实现了这一分发机制:
logical→arrow.array.BooleanArrayuint8/16/32/64→ 对应的UInt*Arrayint8/16/32/64→ 对应的Int*Arraysingle→Float32Array,double→Float64Arraystring(以及cellstr,通过convertCellstrToString支持)→StringArraydatetime→TimestampArray,duration→Time64Arraytable→StructArray,cell→ListArray- 其他类型抛出
arrow:array:UnsupportedMATLABType错误
例如,向arrow.array传入double数组会得到arrow.Float64Array,与文档示例一致。
missing 值自动转为 Arrow NULL
文档特别指出:MATLAB 的missing值(如NaN、NaT、<undefined>)在构造arrow.Array子类实例时会自动转换为 Arrow 的NULL值。这一点在源码中也有迹可循:matlab/src/matlab/+arrow/+array/Array.m 暴露了Valid(有效性位图)依赖属性,并通过NumElements与Valid统计NumNulls用于显示头信息;底层代理(C++ 侧array/proxy/)负责把 MATLAB 的缺失值编码进 Arrow 的有效性位图中。
文档示例的完整运行
设计文档给出了一个可复现的 MATLAB 会话示例:
>> A = randi(100, 1, 5) A = 82 91 13 92 64 >> class(A) ans = 'double' >> A(4) = NaN; % Set the fourth element to NaN. >> AA = arrow.array(A); % Create an arrow.Array from A. >> class(AA) ans = 'arrow.Float64Array' >> AA(3:5) % Extract elements at indices 3 to 5 from AA. ans = 13 <NULL> 64 >> clear AA; % Clear AA from workspace and release Arrow C++ memory.索引AA(3:5)得到13 <NULL> 64,其中索引 4 处正是NaN被转换为 ArrowNULL后的显示结果。clear AA会释放底层 C++ 代理,从而回收 Arrow C++ 内存。从源码看,Array.m中的NumElements、Valid、Type三个依赖属性为这类操作提供了基础元数据,toString/displayScalarObject实现了带<NULL>标记的展示逻辑。
用例详解二(UC2):MATLAB 数据与 Feather 等文件格式的序列化
UC2 的目标是让 MATLAB 数据能够通过 Arrow 内存序列化到磁盘文件(如 Feather、Parquet),并能读回。设计文档给出了从 MATLABtable到 Feather 文件的完整开发流程。
方式一:用arrow.Array组合成arrow.Table
开发者可以先为每个表变量构造arrow.Array,再组合成arrow.Table:
>> Var1 = arrow.array(["foo"; "bar"; "baz"]); >> Var2 = arrow.array([today; today + 1; today + 2]); >> Var3 = arrow.array([10; 20; 30]); >> AT = arrow.Table(Var1, Var2, Var3);方式二:直接从 MATLABtable转换
文档设想了arrow.matlab2arrow这样的转换函数;从当前仓库实现看,这一职责由 matlab/src/matlab/+arrow/table.m 承担——它接收一个 MATLABtable(通过istable校验),调用arrow.tabular.internal.decompose将各列拆解为arrow.Array,收集列名后创建arrow.tabular.Table代理:
>> Weight = [10; 24; 10; 12; 18]; >> Radius = [80; 135; 65; 70; 150]; >> Density = [10.2; 20.5; 11.2; 13.7; 17.8]; >> T = table(Weight, Radius, Density); % Create a MATLAB table >> AT = arrow.table(T); % Create an arrow.Table反向转换则由 matlab/src/matlab/+arrow/+tabular/Table.m 的table()/toMATLAB()方法完成:它逐列取出ChunkedArray并调用toMATLAB,再用makeValidVariableNames与makeValidDimensionNames生成合法的 MATLAB 变量名与维度名后组装回 MATLABtable。
写入与读取 Feather 文件
文档示例中通过arrow.FeatherTableWriter写入、通过arrow.FeatherTableReader读取:
>> featherTableWriter = arrow.FeatherTableWriter(); >> featherTableWriter.write(AT, "data.feather");>> featherTableReader = arrow.FeatherTableReader("data.feather"); >> AT = featherTableReader.read();在当前仓库中,Feather 读写的底层实现位于 C++ 侧 matlab/src/cpp/arrow/matlab/io/feather/proxy/writer.cc 与reader.cc(基于 Arrow C++ 的arrow::ipc::feather能力),MATLAB 侧则由 matlab/src/matlab/+arrow/+internal/+io/+feather/Writer.m 等内部类封装;同时在包根目录提供了面向普通用户的顶层函数 matlab/src/matlab/featherwrite.m 与 matlab/src/matlab/featherread.m,这正是下文"高级工作流"中设想的featherwrite高层接口。
高级用户工作流:编写 MEX 函数扩展写入能力
文档指出,要新增"写入 Feather 文件"这类能力,高级 MATLAB 用户需要:
- 编写一个可被 MATLAB 直接调用的MEX 函数(例如
featherwriteMEX); - 在 MEX 函数内部使用
arrow::matlab::unwrap_table把 MATLAB 侧的arrow.Table转换为 C++ 侧的arrow::Table; - 将 C++
arrow::Table交给 Arrow C++ 库的写入 API(如arrow::ipc::feather::WriteTable)完成落盘。
读取方向(arrow.FeatherTableReader)遵循相同思路。这与仓库中 C++ 侧按"proxy + 面向 MEX 的 gateway"组织的方式一致:mex/gateway.cc是 MEX 入口的枢纽,各功能域的 proxy 类持有真实的 Arrow C++ 对象,MATLAB 调用经libmexclass代理转发到这些 proxy 方法。
赋能高层工作流
设计文档的核心思想是分层:面向高级用户的构建块 API(arrow.*类与 C++ API)+面向日常用户的薄封装(如featherwrite)。文档用一张"代码流图"(Code flow diagram)总结了高级用户需要创作的各个部分——从arrow.Table解包、MEX 函数、调用 Arrow C++ 写入 API,再到暴露给普通 MATLAB 用户的高层函数。当前仓库 matlab/src/matlab/featherwrite.m 与featherread.m的存在即是该"高层工作流"设计的直接产物;测试方面,matlab/test/tfeather.m 以及matlab/test/arrow下的用例对读写往返(roundtrip)进行了验证。
用例详解三(UC3):进程内与进程外的内存共享
Arrow 支持多种本地内存共享方式,文档将其划分为两大类:
- 进程内内存共享(In-Process Memory Sharing)
- 进程外内存共享(Out-of-Process Memory Sharing)
进程内共享:借助 Arrow C Data Interface 与 PyArrow 零拷贝互通
MATLAB 支持在 MATLAB 进程内运行 Python 代码,因此 MATLAB 与 Python 共享同一虚拟地址空间,理论上可以在两者之间高效共享 Arrow 内存。Arrow 为此定义了C Data Interface——一套轻量级的 C API,用于在同一地址空间内的多种语言之间共享 Arrow 数据与元数据。
其核心是两个 C 风格结构体:
ArrowArray:表示 Arrow 数据(内容遵循 Arrow Columnar Format);ArrowSchema:表示相关的元数据(类型、字段等)。
MATLAB → PyArrow 方向:调用arrow.Array的exportToCDataInterface方法,把数组导出为 C Data Interface 格式,得到两个结构体的内存地址;这些地址可以直接传给 Python(不复制底层数据),再通过pyarrow.Array._import_from_c构造pyarrow.Array:
% Create a MATLAB arrow.Array. >> AA = arrow.array([1, 2, 3, 4, 5]); % Export the MATLAB arrow.Array to the C Data Interface format, returning the % memory addresses of the required ArrowArray and ArrowSchema C-style structs. >> [arrayMemoryAddress, schemaMemoryAddress] = AA.exportToCDataInterface(); % Import the memory addresses of the C Data Interface format structs to create a pyarrow.Array. >> PA = py.pyarrow.Array._import_from_c(arrayMemoryAddress, schemaMemoryAddress);PyArrow → MATLAB 方向:先用pyarrow.Array._export_to_c导出,再把地址交给arrow.Array.importFromCDataInterface静态方法零拷贝构造 MATLABarrow.Array:
% Make a pyarrow.Array. >> PA = py.pyarrow.array([1, 2, 3, 4, 5]); % Create ArrowArray and ArrowSchema C-style structs adhering to the Arrow C Data Interface format. >> array = py.pyarrow.cffi.ffi.new("struct ArrowArray*") >> arrayMemoryAddress = py.int(py.pyarrow.cffi.ffi.cast("uintptr_t", array)); >> schema = py.pyarrow.cffi.ffi.new("struct ArrowSchema*") >> schemaMemoryAddress = py.int(py.pyarrow.cffi.ffi.cast("uintptr_t", schema)); % Export the pyarrow.Array to the C Data Interface format, populating the required ArrowArray and ArrowShema structs. >> PA.export_to_c(arrayMemoryAddress, schemaMemoryAddress) % Import the C Data Interface structs to create a MATLAB arrow.Array. >> AA = arrow.Array.importFromCDataInterface(arrayMemoryAddress, schemaMemoryAddress);(第二个示例改编自 PyArrow 的test_cffi.py测试用例。)
在仓库源码中,这一互操作能力有完整的实现支撑:MATLAB 侧 matlab/src/matlab/+arrow/+c/Array.m 是 C Data Interface 中ArrowArray结构体指针的包装(提供Address属性),arrow.c.Schema对应ArrowSchema;Array.m 的export(obj, cArrowArrayAddress, cArrowSchemaAddress)方法把地址传给代理执行导出,而静态方法import(Array.m)借助arrow.c.internal.ArrayImporter完成导入;C++ 侧 matlab/src/cpp/arrow/matlab/c/proxy/array.cc、array_importer.cc、schema.cc则直接操作ArrowArray/ArrowSchema结构体并接入 Arrow C++ 的 C Data Interface 支持。
进程外共享:内存映射 IPC 文件
对于多进程"数据处理流水线"中的大表,文档推荐将arrow.Table序列化为Arrow IPC File Format,再由独立进程中的 PyArrow 对文件做内存映射(memory-mapped)零拷贝读取。由于 IPC File Format 与内存中的 Arrow 格式是磁盘上 1:1 的映射,内存映射读取无需自定义反序列化/转换即可构造pyarrow.Table,性能极高。
% Create a MATLAB arrow.Table. >> Var1 = arrow.array(["foo", "bar", "baz"]); >> Var2 = arrow.array([today, today + 1, today + 2]); >> Var3 = arrow.array([10, 20, 30]); >> AT = arrow.Table(Var1, Var2, Var3); % Write the MATLAB arrow.Table to the Arrow IPC File Format on disk. >> arrow.ipcwrite(AT, "data.arrow"); % Run Python in a separate process. >> pyenv("ExecutionMode", "OutOfProcess"); % Memory map the Arrow IPC File. >> memoryMappedFile = py.pyarrow.memory_map("data.arrow"); % Construct pyarrow.ipc.RecordBatchFileReader to read the Arrow IPC File. >> recordBatchFileReader = py.pyarrow.ipc.open_file(memoryMappedFile); % Read all record batches from the Arrow IPC File in one-shot and return a pyarrow.Table. >> PAT = recordBatchFileReader.read_all()文档设想的arrow.ipcwrite在仓库中的对应实现是 matlab/src/matlab/+arrow/+io/+ipc/RecordBatchFileWriter.m:它接受文件名与arrow.tabular.Schema构造,提供writeRecordBatch、writeTable以及按输入类型自动分发的write方法,并在close时关闭底层文件;读取方向对应 RecordBatchFileReader.m。C++ 侧 matlab/src/cpp/arrow/matlab/io/ipc/proxy/record_batch_file_writer.cc 与record_batch_file_reader.cc封装了 Arrow C++ 的 IPC 读写能力,实现writeTable时会把 MATLAB 的arrow.Table代理转换为 C++arrow::Table后再写入——与 UC2 中"unwrap 后调用 C++ API"的思想一脉相承。
测试、文档与安装策略
测试基础设施
设计文档要求至少包含三类测试基础设施:
- MATLAB 类化单元测试(Class-Based Unit Tests):
matlab/test/目录即为此服务,其中tfeather.m等测试文件覆盖 Feather 读写等核心功能; - MATLAB CI 工作流:仓库
ci/docker/与ci/scripts/中与 MATLAB 相关的构建/测试脚本(如c_glib_test.sh等)配套完成持续集成; - 集成测试(Integration Testing):文档建议参考 Apache Arrow 官方的集成测试格式,验证跨语言的一致性。
实现要点:为了测试内部 C++ 代码,可以用 MEX 函数从 MATLAB 类化单元测试中调用 C++ 代码——这与 UC2 中"MEX 函数作为 MATLAB 与 C++ 桥梁"的设计完全一致。
文档规划
为保证可用性、可发现性与可访问性,设计文档规划了以下文档工作:
- MATLAB API 的Help Text;
- MATLAB API 参考手册;
- MATLAB 与 C++ API 的使用示例;
- 面向构建与安装的 README;
- 构建系统文档;
- CI 集成文档。
安装方式
文档的长期目标是让 MATLAB 用户无需编译 MEX 函数或进行任何手动配置即可安装,其机制与 JavaScript 用户通过npm安装apache-arrow、Rust 用户通过cargo安装arrowcrate 类似——即通过 MATLAB 的Add-On Explorer安装可选软件包。
在尚无可直接安装的 MATLAB Add-On 的短期阶段,规划包括:
- 在仓库中维护最新、清晰、面向近期 MATLAB 版本的构建与安装说明(即 matlab/README.md 与 matlab/CMakeLists.txt 所承载的内容);
- 通过 CI 工作流为 Windows、Mac、Linux 定期构建预编译的 MEX 函数,让用户无需手动从头构建 MEX 接口即可体验最新功能。
路线图
设计文档以一张路线图表总结各能力的开发计划:
| Capability | Use Case | Timeframe |
|---|---|---|
| Arrow Memory Interaction | UC1 | Near Term |
| File Reading/Writing | UC2 | Near Term |
| In/Out-of-Process Memory Sharing | UC3 | Mid Term |
对照当前仓库的代码成熟度,可以推断:UC1 与 UC2 所依赖的数组体系、arrow.table/Table、Feather 读写(featherwrite/featherread)与 IPC 读写(arrow.io.ipc.*)已经具备完整实现;UC3 所依赖的 C Data Interface 导入/导出(arrow.c.Array、Array.export、Array.import)也已在 C++ 与 MATLAB 双层落地。整体上,设计文档描绘的"包化 API + proxy 式 C++ 桥接 + 高层薄封装"的三层架构,在仓库中均有对应的工程实体可供读者进一步深入研读。
【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址: https://gitcode.com/gh_mirrors/arrow13/arrow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考