Apache Arrow C++ CUDA 支持:设备、上下文、GPU 缓冲与 IPC 的完整 API 解析
2026/9/14 7:11:34 网站建设 项目流程

Apache Arrow C++ CUDA 支持:设备、上下文、GPU 缓冲与 IPC 的完整 API 解析

【免费下载链接】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 官方 API 参考文档 CUDA support 展开,结合cpp/src/arrow/gpu/下的实际源码实现,系统讲解arrow::cuda命名空间中五类核心 API:上下文(Contexts)、设备(Devices)、缓冲区(Buffers)、内存输入/输出(Memory Input/Output)与 IPC 函数。读完后你将能够:在 C++ 程序中通过CudaDeviceManager获取 GPU 设备与上下文、分配和搬运显存、以文件流接口零拷贝读写 GPU 缓冲,并借助 CUDA IPC 跨进程共享 GPU 内存与 RecordBatch 消息。

一、API 总览与构建前提

官方文档 docs/source/cpp/api/cuda.rst 将该 API 划分为五个主题,对应的实现集中位于 cpp/src/arrow/gpu/cuda_context.h、cpp/src/arrow/gpu/cuda_memory.h 和 cpp/src/arrow/gpu/cuda_arrow_ipc.h,全部类型定义在arrow::cuda命名空间中:

文档主题对应类型 / 函数实现头文件
ContextsCudaDeviceManagerCudaContextcuda_context.h
DevicesCudaDeviceCudaMemoryManagercuda_context.h
BuffersCudaBufferCudaHostBuffercuda_memory.h
Memory Input/OutputCudaBufferReaderCudaBufferWritercuda_memory.h
IPCCudaIpcMemHandleSerializeRecordBatchReadRecordBatchcuda_memory.h、cuda_arrow_ipc.h

该模块由独立的libarrow_cuda目标产出(见 cpp/src/arrow/gpu/CMakeLists.txt,并生成arrow-cuda.pcArrowCUDAConfig.cmake供下游链接)。从源码结构看,CMake 构建通过ARROW_CUDA选项启用该子目录(见 cpp/src/arrow/CMakeLists.txt 中的if(ARROW_CUDA)守卫,以及 cpp/CMakeLists.txt 中为arrow-cuda.pc准备的ARROW_CUDA_PC_CFLAGS变量,静态链接时追加-DARROW_CUDA_STATIC)。因此构建 C++ 库时需要 CUDA Toolkit 环境并打开相应构建选项;使用方应链接arrow_cuda组件并包含arrow/gpu/下的头文件。

二、Contexts:CudaDeviceManager 与 CudaContext

2.1 CudaDeviceManager —— 进程级单例入口

CudaDeviceManager(cuda_context.h#L44-L85)是所有 CUDA API 的入口点,通过静态方法Instance()获取进程级单例。其公开成员包括:

  • Result<std::shared_ptr<CudaDevice>> GetDevice(int device_number):按 CUDA 逻辑设备号获取CudaDevice实例;
  • Result<std::shared_ptr<CudaContext>> GetContext(int device_number):获取指定设备的上下文(源码注释标明返回的是cached context,即会缓存复用);
  • Result<std::shared_ptr<CudaContext>> GetSharedContext(int device_number, void* handle):使用其他库创建的 CUDA 上下文句柄构造一个“共享”上下文,用于与外部库互操作;
  • Result<std::shared_ptr<CudaHostBuffer>> AllocateHost(int device_number, int64_t nbytes):分配对指定 GPU 可快速访问的主机内存;
  • Status FreeHost(void* data, int64_t nbytes):释放AllocateHost分配的内存(源码明确约束:传入指针必须来自AllocateHost);
  • int num_devices() const:返回可见 CUDA 设备数量。

其构造函数为 private,配合静态成员instance_,从源码结构看这是一个线程安全的惰性初始化单例。

2.2 CudaContext —— CUDA 驱动 API 的对象化封装

CudaContext(cuda_context.h#L319-L415)的注释将其定位为“low-level CUDA driver API 的面向对象接口”。它持有私有实现(pimpl,std::shared_ptr<Impl> impl_),关键公开能力有:

  • 显存生命周期Allocate(int64_t nbytes)在上下文的 GPU 设备上分配显存,返回CudaBufferFree(void* device_ptr, int64_t nbytes)释放显存;View(uint8_t* data, int64_t nbytes)把外部显存包装成CudaBuffer视图——注释特别强调调用者负责该内存的分配/释放,且必须属于本上下文的 CUDA context;bytes_allocated()可查询已分配字节数;
  • IPC 支持OpenIpcBuffer(const CudaIpcMemHandle&)打开其他进程导出的 IPC 句柄并映射到本地显存,CloseIpcBuffer(CudaBuffer*)关闭映射(见 2 节之后的 IPC 章节);
  • 同步与句柄Synchronize()阻塞直到该设备所有任务完成;handle()把 CUDA 上下文句柄暴露给其他库;Close()关闭上下文;
  • 地址转换GetDeviceAddress(uint8_t*/uintptr_t addr)返回“可从本上下文 kernel 访问的设备地址”。注释解释了设备地址不一定是显存地址——用cudaMallocHost/cudaHostAlloc分配的主机内存、cudaMallocManaged分配的托管内存、或经cudaHostRegister页锁定的主机内存,都可以给出设备可达地址;
  • 关联查询memory_manager()返回该上下文设备对应的默认CudaMemoryManagerdevice()/device_number()返回关联设备及其逻辑号。

从源码结构看,CudaContext还封装了私有的ExportIpcBufferCopyHostToDevice/CopyDeviceToHost/CopyDeviceToDevice/CopyDeviceToAnotherDevice等拷贝路径(cuda_context.h#L387-L401),这些是CudaBuffer拷贝方法与跨设备CopyBufferFrom/To的底层执行者。

三、Devices:CudaDevice 与 CudaMemoryManager

3.1 CudaDevice —— 与具体 GPU 绑定的 Device 实现

CudaDevice(cuda_context.h#L91-L240)继承自 Arrow 的Device抽象,每个实例绑定一个以逻辑号标识的 CUDA 设备,且device_type()固定返回DeviceAllocationType::kCUDA。核心接口:

  • static Result<std::shared_ptr<CudaDevice>> Make(int device_number):按设备号构造;
  • 信息查询:device_number()(逻辑号)、device_name()(GPU 型号名)、total_memory()(设备总显存)、handle()(原始CUdevice句柄,注释说明可用于向其他库暴露该设备);
  • 上下文获取:GetContext()返回与该设备主 CUDA 上下文(primary context)关联的上下文——注释明确这是推荐方式,因为主上下文 API 可与任何使用该 API 的库透明互操作;GetSharedContext(void* handle)则用于互操作非主上下文的库,且句柄不被拥有(销毁CudaContext时不会释放);
  • AllocateHostBuffer(int64_t size):用该设备的主上下文分配主机驻留、GPU 可访问的缓冲;
  • default_memory_manager():返回该设备的默认CudaMemoryManager

此外CudaDevice内嵌两个面向并发执行的类:

  • CudaDevice::Stream(标注为 EXPERIMENTAL):包装CUstream。它不拥有CUstream对象,需由外部通过cuStreamCreate/cuStreamDestroy管理;默认构造使用 CUDA 默认流,并刻意禁用从字面量0nullptr构造(cuda_context.h#L151-L183)。提供WaitEventSynchronize,且可显式转换为CUstream
  • CudaDevice::SyncEvent:包装CUevent,提供Wait()(阻塞直到事件完成)与Record(const Device::Stream&)(在流上记录事件,流完成后触发)。

设备级创建流与事件的统一入口包括MakeStream()/MakeStream(unsigned int flags)/WrapStream(void*, release_fn)CudaMemoryManager::MakeDeviceSyncEvent()/WrapDeviceSyncEvent()

源码还提供了类型判别与转换工具:IsCudaDevice(const Device&)AsCudaDevice(const std::shared_ptr<Device>&)(cuda_context.h#L242-L250),后者在非CudaDevice时返回错误。

3.2 CudaMemoryManager —— GPU 侧的 MemoryManager

CudaMemoryManager(cuda_context.h#L253-L304)继承MemoryManager,是与某个CudaDevice绑定的内存分配器:

  • AllocateBuffer(int64_t size):在 GPU 上分配 ArrowBuffer(即CudaBuffer);
  • GetBufferReader/GetBufferWriter:为缓冲构造 4 节所述的CudaBufferReader/CudaBufferWriter文件流接口;
  • cuda_device():返回绑定的CudaDevice具体类型指针,省去对device()结果的向下转型;
  • 跨管理器拷贝/视图:CopyBufferFrom/CopyBufferTo/CopyNonOwnedFrom/CopyNonOwnedTo/ViewBufferFrom/ViewBufferTo。其中跨设备拷贝最终落到CudaContext::CopyDeviceToAnotherDevice(可推断自 friend 声明与私有拷贝方法的设计);
  • 同步事件:MakeDeviceSyncEvent()(内部cuEventCreate,析构时cuEventDestroy)与WrapDeviceSyncEvent()(包装外部CUevent*,可传 no-op 表示所有权在外部)。

对应地,IsCudaMemoryManager/AsCudaMemoryManager用于判别与转型(cuda_context.h#L306-L315)。

四、Buffers:CudaBuffer 与 CudaHostBuffer

4.1 CudaBuffer —— 位于 GPU 设备上的 Arrow Buffer

CudaBuffer(cuda_memory.h#L39-L109)继承自Buffer,代表一块 GPU 显存。头文件顶部的注释给出了重要提醒:“Be careful using this in any Arrow code which may not be GPU-aware”——把它误传给假定 CPU 内存的 Arrow 代码会造成问题。

构造方式有三种:直接持有/不持有显存(own_data)的两种构造(含is_ipc标记表示内存由 IPC 映射而来)、以及从父CudaBuffer切出子视图(parent + offset + size)。主要成员:

  • static Result<std::shared_ptr<CudaBuffer>> FromBuffer(std::shared_ptr<Buffer>):把通用Buffer转回CudaBuffer;若底层并非 GPU 内存则返回错误——这是从普通 Arrow 数据中识别 GPU 缓冲的标准入口;
  • 主机↔设备拷贝CopyToHost(position, nbytes, out)从 GPU 拷到主机;CopyFromHost(position, data, nbytes)从主机拷入;CopyFromDevice(position, data, nbytes)做设备到设备拷贝(注释假设源与目标显存同属一个上下文);CopyFromAnotherDevice(src_ctx, position, data, nbytes)则显式指定源上下文,用于跨设备拷贝;
  • IPC 导出ExportForIpc()把显存暴露为可被其他进程使用的 IPC 内存并返回CudaIpcMemHandle。注释指出一个关键语义变化:调用后该显存不会CudaBuffer析构而释放(生命周期交由 IPC 协议管理)。

4.2 CudaHostBuffer 与页锁定主机内存

CudaHostBuffer(cuda_memory.h#L113-L121)继承MutableBuffer,注释说明其内存由cudaHostAlloc创建(即页锁定主机内存),GPU 可快速访问。其GetDeviceAddress(const std::shared_ptr<CudaContext>& ctx)返回 GPU kernel 可直接读取该内存的设备地址。

命名空间级辅助函数补全了该能力面(cuda_memory.h#L245-L264):

  • AllocateCudaHostBuffer(int device_number, int64_t size):为指定设备分配 CPU 侧、GPU 快速可访问的内存,注释强调 GPU 对该内存享有“fast memory copy”优势;
  • GetDeviceAddress(const uint8_t* cpu_data, const std::shared_ptr<CudaContext>& ctx):低层函数,由 CPU 地址求设备地址;
  • GetHostAddress(uintptr_t device_ptr):低层函数,由设备地址求 CPU 侧可访问地址。

五、Memory Input / Output:CudaBufferReader 与 CudaBufferWriter

5.1 CudaBufferReader —— 面向零拷贝读取

CudaBufferReader(cuda_memory.h#L162-L202)继承 Arrow 的随机读取文件抽象(经RandomAccessFileConcurrencyWrapper包装以支持并发)。类注释中包含必须理解的语义边界:

  • 读入Buffer的 Read 返回指向设备内存的Buffer(零拷贝),通常与期望 CPU 缓冲的 Arrow 代码不兼容;
  • 读入裸指针的 Read 则把设备内存拷贝到主机目标区域。

公开接口包括supports_zero_copy()buffer()(返回底层CudaBuffer)、DoRead/DoReadAt(两个重载分别对应“读到裸指针”与“读到 Buffer”两种语义)以及DoTell/DoSeek/DoGetSize。因此它适合作为 GPU 侧数据的io::RandomAccessFile使用,例如直接解码已完全落在显存中的 IPC 消息(见 IPC 章节的ReadRecordBatch)。

5.2 CudaBufferWriter —— 可带 CPU 缓冲区的写入

CudaBufferWriter(cuda_memory.h#L206-L243)继承io::WritableFile,支持Write/WriteAt/Seek/Tell/Flush/Close。其缓冲控制是本类的特色:

  • 默认无缓冲写入,每次Write直接触发一次cudaMemcpy
  • SetBufferSize(int64_t buffer_size)分配指定大小的 CPU 缓冲来减少cudaMemcpy调用次数(注释原话:“Set CPU buffer size to limit calls to cudaMemcpy”);
  • buffer_size()返回 CPU 缓冲大小(0 表示无缓冲),num_bytes_buffered()返回当前缓存在主机侧的字节数。

这一设计与高频小写入场景直接相关:先聚批在 CPU 再整体拷贝上 GPU,是减少 PCIe 传输开销的典型手段。

六、IPC:CudaIpcMemHandle 与 RecordBatch 的 GPU 序列化

文档将 IPC 分为CudaIpcMemHandle类型与一组自由函数(源码中用 Doxygen 组cuda-ipc-functions标注,见 cuda_arrow_ipc.h#L44)。

6.1 CudaIpcMemHandle —— CUDA IPC 句柄容器

CudaIpcMemHandle(cuda_memory.h#L125-L152)封装 CUDA 驱动的CUipcMemHandle

  • static FromBuffer(const void* opaque_handle):从另一进程传来的不透明句柄缓冲(序列化后的CUipcMemHandle字节)构造;
  • Serialize(MemoryPool* pool = default_memory_pool()):把句柄写进一个可传输的Buffer——这是跨进程传递句柄的标准通道;
  • 内部保留memory_size()handle(),由CudaBufferCudaContext以 friend 关系访问。

6.2 完整 IPC 工作流

结合 4.1 与 2.2 的方法,跨进程共享 GPU 内存的调用链是:

  1. 导出方CudaBuffer::ExportForIpc()得到CudaIpcMemHandle,经Serialize()变成可序列化Buffer,通过任何进程间通道发送;
  2. 接收方CudaIpcMemHandle::FromBuffer(收到字节)还原句柄,再由目标设备的CudaContext::OpenIpcBuffer(handle)得到映射到本地显存的CudaBuffer
  3. 清理:不再使用时调用CudaContext::CloseIpcBuffer(buffer)解除映射。

该流程在测试中有直接印证:cpp/src/arrow/gpu/cuda_test.cc 中device_buffer->ExportForIpc()与后续OpenIpcBuffer的配对用法,覆盖了导出—序列化—打开—关闭的完整闭环。

6.3 RecordBatch 级 GPU 序列化函数

cuda-ipc-functions组提供两个面向 Arrow 数据的自由函数(cuda_arrow_ipc.h#L52-L67):

  • SerializeRecordBatch(const RecordBatch& batch, CudaContext* ctx):把一个 RecordBatch 序列化为 IPC 消息并直接写入 GPU 显存,返回CudaBuffer。这是把数据从主机 Arrow 结构搬到 GPU 的入口;
  • ReadRecordBatch(const std::shared_ptr<Schema>& schema, const ipc::DictionaryMemo* dictionary_memo, const std::shared_ptr<CudaBuffer>& buffer, MemoryPool* pool = default_memory_pool())ReadRecordBatch的 GPU 特化版本,输入是完全位于显存的CudaBufferIPC 消息。注意其pool参数的用途——注释说明它用于为元数据在主机侧分配空间,数据本体保持在设备上;dictionary_memo可传nullptr(当确定没有字典编码字段时)。

这两个函数与 5.1 的CudaBufferReader配合,可以构建完全驻留 GPU 的“序列化—零拷贝解码”路径,避免中间结果反复过 PCIe。

七、使用注意事项小结

  • GPU 感知前提CudaBuffer的类注释明确提醒它不应流入“可能不感知 GPU”的 Arrow 代码;凡从CudaBufferReader读到Buffer的零拷贝路径同样适用该约束(cuda_memory.h#L39、cuda_memory.h#L157-L161)。
  • 所有权语义CudaContext::View创建的缓冲不拥有内存,调用者负责分配/释放;GetSharedContext的句柄不被拥有;CudaDevice::Stream不拥有CUstream。这些“非拥有”包装都要求外部正确管理底层 CUDA 资源。
  • 主上下文优先CudaDevice::GetContext()使用主上下文是官方注释推荐的互操作方式;仅在与使用非主上下文的库协作时才用GetSharedContext
  • IPC 生命周期ExportForIpc()之后显存不再随CudaBuffer析构释放,接收方须调用CloseIpcBuffer完成映射释放,否则会造成资源滞留。
  • 写入缓冲:高频小写入场景下用CudaBufferWriter::SetBufferSize启用 CPU 侧聚批,减少cudaMemcpy次数。

以上所有接口均出自arrow::cuda命名空间,构建时需启用 CUDA 组件(CMake 选项ARROW_CUDA,见 cpp/src/arrow/CMakeLists.txt),并链接arrow_cuda目标;完整的测试覆盖位于 cpp/src/arrow/gpu/cuda_test.cc,性能基准则在同目录的cuda_benchmark.cc中维护,可作为行为验证与性能对照的起点。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询