我使用 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/