c++ - Cython:如何移动大对象而不复制它们?

标签 c++ python memory-management cython

我使用 Cython 包装 C++ 代码并将其公开给 Python 以进行交互工作。我的问题是我需要从文件中读取大图(几千兆字节),它们最终在内存中出现了两次。谁能帮我诊断并解决这个问题?

我的图形类 Cython 包装器如下所示:

cdef extern from "../src/graph/Graph.h":
    cdef cppclass _Graph "Graph":
        _Graph() except +
        _Graph(count) except +
        count numberOfNodes() except +
        count numberOfEdges() except +


cdef class Graph:
    """An undirected, optionally weighted graph"""
    cdef _Graph _this

    def __cinit__(self, n=None):
        if n is not None:
            self._this = _Graph(n)

    # any _thisect which appears as a return type needs to implement setThis
    cdef setThis(self, _Graph other):
        #del self._this
        self._this = other
        return self

    def numberOfNodes(self):
        return self._this.numberOfNodes()

    def numberOfEdges(self):
        return self._this.numberOfEdges()

如果需要返回一个 Python Graph,需要将其创建为空,然后使用 setThis 方法设置原生 _Graph 实例。例如,当从文件中读取 Graph 时,就会发生这种情况。这是这个类的工作:

cdef extern from "../src/io/METISGraphReader.h":
    cdef cppclass _METISGraphReader "METISGraphReader":
        _METISGraphReader() except +
        _Graph read(string path) except +

cdef class METISGraphReader:
    """ Reads the METIS adjacency file format [1]
        [1]: http://people.sc.fsu.edu/~jburkardt/data/metis_graph/metis_graph.html
    """
    cdef _METISGraphReader _this

    def read(self, path):
        pathbytes = path.encode("utf-8") # string needs to be converted to bytes, which are coerced to std::string
        return Graph(0).setThis(self._this.read(pathbytes))

交互式用法如下所示:

 >>> G = graphio.METISGraphReader().read("giant.metis.graph")

从文件读取完成并使用 X GB 内存后,有一个明显复制发生的阶段,之后使用 2X GB 内存。当 del G 被调用时,整个内存被释放。

我的错误在哪里导致图形被复制并在内存中存在两次?

最佳答案

我没有给你一个明确的答案,但我有一个理论。

您编写的 Cython 包装器很不寻常,因为它们直接包装 C++ 对象而不是指向它的指针。

下面的代码效率特别低:

cdef setThis(self, _Graph other):
    self._this = other
    return self 

原因是您的 _Graph 类包含多个 STL vector ,必须复制这些 vector 。因此,当您的 other 对象被分配给 self._this 时,内存使用量实际上增加了一倍(或者更糟,因为 STL 分配器可能出于性能原因过度分配)。

我编写了一个与您的相匹配的简单测试,并在各处添加了日志记录,以查看对象是如何创建、复制或销毁的。我在那里找不到任何问题。拷贝确实发生了,但在分配完成后,我看到只剩下一个对象。

所以我的理论是,您看到的额外内存与 vector 中的 STL 分配器逻辑有关。所有这些额外的内存必须附加到拷贝之后的最终对象。

我的建议是您切换到更标准的基于指针的包装。您的 _Graph 包装器应该或多或少地定义如下:

cdef class Graph:
    """An undirected, optionally weighted graph"""
    cdef _Graph* _this

    def __cinit__(self, n=None):
        if n is not None:
            self._this = new _Graph(n)
        else:
            self._this = 0

    cdef setThis(self, _Graph* other):
        del self._this
        self._this = other
        return self

    def __dealloc__(self):
        del self._this

请注意,我需要删除 _this,因为它是一个指针。

然后您需要修改您的 METISGraphReader::read() 方法以返回堆分配的 Graph。该方法的原型(prototype)应改为:

Graph* METISGraphReader::read(std::string path);

那么它的 Cython 包装器可以写成:

    def read(self, path):
        pathbytes = path.encode("utf-8") # string needs to be converted to bytes, which are coerced to std::string
        return Graph().setThis(self._this.read(pathbytes))

如果您这样做,则只有一个对象,即由 read() 在堆上创建的对象。指向该对象的指针返回给 read() Cython 包装器,然后将其安装在全新的 Graph() 实例中。唯一被复制的是指针的 4 或 8 个字节。

希望对您有所帮助!

关于c++ - Cython:如何移动大对象而不复制它们?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20123696/

相关文章:

java - JNI 中如何处理全局变量?

c++ - 是否可以编写仅提及特殊复制成员的构造函数赋值运算符?

python - 在 Python 中截取 url 的更好方法

Python 列表排序取决于项目是否在另一个列表中

windows - 保留内存和提交内存有什么区别?

c# - 是否可以沙盒化并运行在浏览器的文本字段中输入的 C++ 或 C# 代码?

C++ 打印速度比 C 快得多

python - 获取 pandas.read_csv 将空字段读取为 NaN,将空字符串读取为空字符串

java - 以编程方式获取android设备的所有RAM内存,而不仅仅是分配给用户进程的内容

c++ - C++ 删除操作符如何找到多态对象的内存位置?