4

Problem

I have a very large std::vector that gets returned from a C++ function, let's call it getVector().

Now I want to wrap that function in Cython:

cdef extern from "XY.h":
    cdef cppclass _XY:
        vector[double] getVector() except +

cdef class XY:
    cdef _XY _this

    ...

    def getVector():
        return self._this.getVector()

As I want to avoid copying this large vector, I would like to make use of std::move. Like this:

cdef extern from "<utility>" namespace "std":
    vector[double] move(vector[double]) # Cython has no function templates

This modifies the Cython source code in the following way:

def getVector():
    return move(self._this.getVector())

Question

The above idea is not working. Cython is (at least) producing 1 copy of the vector. I assume this is because there is no way to move from a vector as this is already a Cython wrapper around the actual std::vector class from C++.

Is there another approach to avoid any copies? I would like to avoid returning a pointer from the C++ method.

There is probably a way by defining a C++ wrapper class that stores the vector and then move this class in Cython but I was wondering whether there is a way without (or very little) modifying the C++ source code.

xZA
  • 153
  • 1
  • 7
  • How are you calling getVector()? If you aren't assigning the result of getVector() to another vector then there's no way to avoid a copy. `cdef vector[double] x; x(getVector()) ` should avoid a copy – dustyrockpyle Apr 10 '15 at 23:49
  • 3
    Actually because you've defined getVector as a def function, the return value will automatically be coerced to a list, and there's no way to avoid a copy in that case. You should probably store the vector in your class and create a numpy array from &your_vector[0], and return the numpy array instead. – dustyrockpyle Apr 10 '15 at 23:57
  • I am simply assigning it to a normal python variable in iPython: `x = mymodule.getVector()`. As far as I understand there is no way to use the cython vector in normal python, so is there a way around the copy if I want to use it in python? Edit: Thank you, I will try this. – xZA Apr 11 '15 at 00:02
  • I am having trouble converting the vector to an numpy array. My vector actually has the form vector[pair[pair[node, node], double]] and I am not sure this can be easily converted to numpy. – xZA Apr 11 '15 at 00:15
  • Oh wow, no it certainly cant. To avoid the copies it would be better to simply implement any manipulations you need to do to the vector as methods in your XY cython class. I think helping further will require a better understanding of what you're trying to do - feel free to PM me. – dustyrockpyle Apr 11 '15 at 13:54
  • @dustyrockpyle I am trying to move a vector of custom objects returned from a C++ method in Cython and then iterate them. – gansub Aug 22 '19 at 18:46

1 Answers1

2

Edit: After @DavidW's warning I realized I misunderstood your question. Below answer just let's you use a templated move from cython directly without explicit declaration for each moving type (e.g. the one you declared for std::vector<double> in your question).


You can use this helper function for wrapping std::move call:

# distutils: language = c++

cdef extern from * namespace "polyfill":
    """
    namespace polyfill {

    template <typename T>
    inline typename std::remove_reference<T>::type&& move(T& t) {
        return std::move(t);
    }

    template <typename T>
    inline typename std::remove_reference<T>::type&& move(T&& t) {
        return std::move(t);
    }

    }  // namespace polyfill
    """
    cdef T move[T](T)

Example usage:

# distutils: language = c++

cdef extern from *:
    """
    #include <iostream>

    #define PRINT() std::cout << __PRETTY_FUNCTION__ << std::endl

    struct Test {
        Test() { PRINT(); }
        ~Test() { PRINT(); }
        Test(const Test&) { PRINT(); }
        Test(Test&&) { PRINT(); }
        Test& operator=(const Test&) { PRINT(); return *this; }
        Test& operator=(Test&&) { PRINT(); return *this; }
    };

    void f(const Test&) { PRINT(); }
    void f(Test&&) { PRINT(); }
    """

    cdef cppclass Test:
        pass

    cdef void f(Test)

from move cimport move

cdef Test t1, t2

print("# t1 = t2")
t1 = t2

print("# t1 = move(t2)")
t1 = move(t2)

print("# f(t1)")
f(t1)

print("# f(move(t1))")
f(move(t1))

print("# f(move(move(t1)))")
f(move(move(t1)))

print("# f(move(move(move(t1))))")
f(move(move(move(t1))))

Output (compiled using cythonize -3 -i test.pyx with Cython 0.29.12 and Python 3.7.3):

$ python3 -c "import test"
Test::Test()
Test::Test()
# t1 = t2
Test& Test::operator=(const Test&)
# t1 = move(t2)
Test& Test::operator=(Test&&)
# f(t1)
void f(const Test&)
# f(move(t1))
void f(Test&&)
# f(move(move(t1)))
void f(Test&&)
# f(move(move(move(t1))))
void f(Test&&)
Test::~Test()
Test::~Test()

Note that C++ objects are still default initialized since that's what Cython does currently, yet this helper function allows calling move assignment after initialization.


Edit: I packaged this snippet as cymove.

ofo
  • 1,160
  • 10
  • 21
  • Could you explain what your wrapper does? It seems to me that `std::move` should work without it (although in the question you answered it fails because the vector is being copied to a Python list, as in dustrockpyle's comment) – DavidW Jul 25 '19 at 07:23
  • @DavidW Cython explicitly provides template arguments when making calls to Cython templated functions. Example: If I declare `std::move` as `cdef T move(T)`, cython translates `move(t1)` as `std::move(t1)`, which causes compiler error since function parameter `t1` trying to bind is of type `Test&&` for `std::move` and `t1` cannot bind to an rvalue reference. I wrote a bit more detailed explanation in a related [GH issue](https://github.com/cython/cython/issues/2169#issuecomment-514397866). – ofo Jul 25 '19 at 18:57
  • @DavidW You are right about that this doesn't solve OP's issue. I will update the answer. – ofo Jul 25 '19 at 18:58
  • Typo in above comment: `cdef T move(T)` should have been `cdef T move[T](T)` – ofo Jul 25 '19 at 19:58