Cython:如何移动大型对象而不复制它们
Cython: How to move large objects without copying them?
我使用Cython包装C++代码,并将其公开给Python以进行交互式工作。我的问题是,我需要从文件中读取大的图形(几GB),它们最终会在内存中出现两次。有人能帮我诊断和解决这个问题吗?
我的图类的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向量,这些向量必须被复制。因此,当您的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
注意,我需要删除_this
,因为它是一个指针。
然后,您需要修改METISGraphReader::read()
方法,以返回堆分配的Graph
。这种方法的原型应该改为:
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++类以通过shared_ptr存储其数据。确保你有一个合适的复制构造函数和赋值运算符:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <memory>
struct Data { // your graph data
Data(const char* _d = NULL) {
if (_d)
strncpy(d, _d, sizeof(d)-1);
else
memset(d, 0, sizeof(d));
}
Data(const Data& rhs) {
memcpy(d, rhs.d, sizeof(d));
}
~Data() {
memset(d, 0, sizeof(d));
}
void DoSomething() { /* do something */ } // a public method that was used in Python
char d[1024];
};
class A { // the wrapper class
public:
A() {}
A(const char* name) : pData(new Data(name)) {}
A(const A& rhs) : pData(rhs.pData) {}
A& operator=(const A& rhs) {
pData = rhs.pData;
return *this;
}
~A() {}
// interface with Data
void DoSomething() {
if (pData.get() != NULL)
pData->DoSomething();
}
private:
std::shared_ptr<Data> pData;
};
int main(int argc, char** argv)
{
A o1("Hello!");
A o2(o1);
A o3;
o3 = o2;
return 0;
}
如果您的约束/目标是"在一台电脑上,在合理的时间内,在具有数十亿条边的图上进行计算",请考虑重构以利用GraphChi。
如果单机/内存不是一个限制,可以考虑利用像Neo4j这样的图形数据库,而不是将所有数据拉入内存。还有一些图形API覆盖Hadoop(例如ApacheGiraph)。
- 为什么C++在将一个对象复制到另一个对象时需要对这两个对象进行低级常量限定
- 如何在向量列表初始化时避免对象复制以及如何延长临时的生存期
- 了解在 C++ 中分配容器时的对象复制
- C++类对象复制构造函数和运算符=
- 复制 elision/RVO 会导致从同一对象复制/移动吗?
- 如何在循环内将大对象复制到omp任务中?
- 为什么当我添加一个不同的对象(复制构造函数中的参数)时调用复制构造函数?
- 无法将类构造函数中新创建的对象复制到 C++ 中的向量成员
- 将C++对象复制到指向新指针的指针处
- 将一个 OpenCV OutputArrayOfArrays 对象复制到另一个对象
- 选择性地禁用隐式对象复制
- 不能从文件到指针向量的对象复制
- 从派生对象复制构造
- OpenCV Mat对象复制速度更快
- 将子对象复制到父对象
- 为什么普通的 c++ 编译器不优化对象复制?
- C :将一个对象复制到构造函数中的另一个对象
- 使用命名对象复制构造函数
- 不必要的对象复制 - C++ STL
- 如何将一个类的对象复制到另一个类中