如何通过Python C API高效传递大量数据?
批量从C向Python传递大数据的最优方案
针对百万级double类型数据的批量传递,核心思路是利用**缓冲区协议(Buffer Protocol)**直接共享或批量写入数据,避免逐个创建PyObject的巨大开销。以下是几种实用方案:
方案1:使用Python标准库array模块
array.array原生支持缓冲区协议,能直接将C中的连续内存块写入,无需逐个元素调用API。
C代码示例
#include <Python.h> #include <arrayobject.h> #include <string.h> PyObject* pass_large_doubles(double* data, size_t count) { // 导入array模块 PyObject* array_module = PyImport_ImportModule("array"); if (!array_module) return NULL; // 获取array.array类 PyObject* array_type = PyObject_GetAttrString(array_module, "array"); Py_DECREF(array_module); if (!array_type) return NULL; // 创建array对象,类型码'd'对应double PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, PyUnicode_FromString("d")); PyObject* array_obj = PyObject_CallObject(array_type, args); Py_DECREF(args); Py_DECREF(array_type); if (!array_obj) return NULL; // 获取缓冲区 Py_buffer view; if (PyObject_GetBuffer(array_obj, &view, PyBUF_WRITABLE | PyBUF_FORMAT) != 0) { Py_DECREF(array_obj); return NULL; } // 确保缓冲区类型匹配 if (strcmp(view.format, "d") != 0) { PyBuffer_Release(&view); Py_DECREF(array_obj); PyErr_SetString(PyExc_TypeError, "Array format mismatch"); return NULL; } // 调整array大小以容纳数据 PyObject* resize_method = PyObject_GetAttrString(array_obj, "resize"); if (!resize_method) { PyBuffer_Release(&view); Py_DECREF(array_obj); return NULL; } args = PyTuple_New(1); PyTuple_SetItem(args, 0, PyLong_FromSize_t(count)); PyObject_CallObject(resize_method, args); Py_DECREF(args); Py_DECREF(resize_method); // 重新获取缓冲区(resize后可能地址变化) PyBuffer_Release(&view); if (PyObject_GetBuffer(array_obj, &view, PyBUF_WRITABLE | PyBUF_FORMAT) != 0) { Py_DECREF(array_obj); return NULL; } // 批量拷贝数据 memcpy(view.buf, data, count * sizeof(double)); // 释放缓冲区 PyBuffer_Release(&view); return array_obj; }
方案2:使用numpy数组(推荐用于高性能场景)
numpy的C API支持直接从C内存创建数组,开销极低,适合超大规模数据。
C代码示例
#include <Python.h> #include <numpy/arrayobject.h> PyObject* pass_large_doubles_to_numpy(double* data, size_t count, int should_own) { // 初始化numpy C API import_array(); // 指定数组维度:1维,长度为count npy_intp dims[1] = {count}; // 创建数组,NPY_DOUBLE对应double类型 PyObject* np_array = PyArray_SimpleNewFromData( 1, dims, NPY_DOUBLE, data ); if (!np_array) return NULL; // 如果需要numpy接管内存(避免C侧手动释放),设置OWNDATA标志 if (should_own) { ((PyArrayObject*)np_array)->flags |= NPY_ARRAY_OWNDATA; } return np_array; }
注意:如果设置
should_own=1,后续numpy会负责释放data内存,C侧不能再手动free;否则需要C侧自行管理内存生命周期。
方案3:自定义缓冲区对象(灵活扩展)
如果需要更定制化的内存管理,可自定义一个实现缓冲区协议的PyObject类型,让Python直接访问C中的原始数据:
核心结构示例
typedef struct { PyObject_HEAD double* data; size_t count; int own_data; } CustomBufferObject; // 实现bf_getbuffer方法 static int custom_buffer_getbuffer(PyObject* self, Py_buffer* view, int flags) { CustomBufferObject* obj = (CustomBufferObject*)self; return PyBuffer_FillInfo( view, self, obj->data, obj->count * sizeof(double), 0, flags // 0表示数据是C顺序 ); } // 实现bf_releasebuffer方法 static void custom_buffer_releasebuffer(PyObject* self, Py_buffer* view) { // 若需要,在这里释放内存 CustomBufferObject* obj = (CustomBufferObject*)self; if (obj->own_data) { free(obj->data); obj->data = NULL; } } // 类型定义 static PyBufferProcs custom_buffer_as_buffer = { custom_buffer_getbuffer, custom_buffer_releasebuffer }; static PyTypeObject CustomBufferType = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "custombuffer.CustomBuffer", .tp_basicsize = sizeof(CustomBufferObject), .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_BUFFER, .tp_as_buffer = &custom_buffer_as_buffer, // 省略其他方法(如__dealloc__) };
这三种方案都能避免百万次API调用,其中numpy方案性能最优,array方案无需额外依赖,自定义方案适合特殊内存管理需求。
内容的提问来源于stack exchange,提问作者Boulder_on
相关产品推荐
相关产品推荐

