cls*_*udt 7 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()
Run Code Online (Sandbox Code Playgroud)
如果需要返回Python Graph,则需要将其创建为空,然后使用该setThis方法设置本机_Graph实例.例如,当Graph从文件中读取a时会发生这种情况.这是这堂课的工作:
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))
Run Code Online (Sandbox Code Playgroud)
交互式用法如下所示:
>>> G = graphio.METISGraphReader().read("giant.metis.graph")
Run Code Online (Sandbox Code Playgroud)
在完成从文件读取并使用X GB内存之后,存在明显复制发生的阶段,之后使用2X GB内存.del G调用时释放整个内存.
我的错误导致图表被复制并在内存中存在两次?
我没有给你明确的答案,但我有一个理论。
您编写的 Cython 包装器很不寻常,因为它们直接包装 C++ 对象而不是指向它的指针。
下面的代码效率特别低:
cdef setThis(self, _Graph other):
self._this = other
return self
Run Code Online (Sandbox Code Playgroud)
原因是您的_Graph类包含多个 STL 向量,并且必须复制它们。因此,当您的other对象被分配给内存时self._this,内存使用量实际上会增加一倍(或更糟,因为 STL 分配器可能会出于性能原因而过度分配)。
我编写了一个与您的测试相匹配的简单测试,并在各处添加了日志记录,以查看对象是如何创建、复制或销毁的。我在那里找不到任何问题。副本确实发生了,但分配完成后,我看到只剩下一个对象。
所以我的理论是,您看到的额外内存与向量中的 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
Run Code Online (Sandbox Code Playgroud)
请注意,我需要删除,_this因为它是一个指针。
然后,您需要修改您的METISGraphReader::read()方法以返回分配的堆Graph。该方法的原型应改为:
Graph* METISGraphReader::read(std::string path);
Run Code Online (Sandbox Code Playgroud)
那么它的 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))
Run Code Online (Sandbox Code Playgroud)
如果您这样做,则只有一个对象,即由 . 在堆上创建的对象read()。指向该对象的指针将返回到read()Cython 包装器,然后将其安装在全新的Graph()实例中。唯一被复制的是指针的 4 或 8 字节。
我希望这有帮助!