簡體   English   中英

Cython:如何移動大型對象而不復制它們?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM