[英]Cython: How to move large objects without copying them?
我使用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向量,並且必須復制這些向量。 因此,當您的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;
}
如果您的約束/目標是“在合理的時間內,在單個PC上計算具有數十億邊緣的圖形。” ,請考慮重構以利用GraphChi 。
如果單機/內存不是約束,請考慮利用像Neo4j這樣的圖形數據庫,而不是將所有數據都拉入內存。 還有與Hadoop重疊的圖形API(例如Apache Giraph )。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.