Fast (no copy) initialization of NumPy array from a C++ vector (or pointer to one)?

1,087 views
Skip to first unread message

Will Mayner

unread,
Jun 11, 2015, 6:17:56 PM6/11/15
to cython...@googlegroups.com
I'm using Cython to wrap a C++ function that returns a 3D vector, and I want to efficiently (without copying) convert it to a NumPy array and return it from the Cython wrapper function for use in Python. What's the proper way to do this?

I've looked on StackOverflow, and I've found advice on how to go from NumPy array to C++ vector, but not much on going the other direction. Any help would be greatly appreciated!

Cheers,
Will

Sturla Molden

unread,
Jun 11, 2015, 6:24:05 PM6/11/15
to cython...@googlegroups.com
There is no such thing as a "3D vector" in C++, so it all depends on how
your "3D vector" is represented.

Sturla
> --
>
> ---
> You received this message because you are subscribed to the Google
> Groups "cython-users" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to cython-users...@googlegroups.com
> <mailto:cython-users...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout.


Will Mayner

unread,
Jun 11, 2015, 8:35:58 PM6/11/15
to cython...@googlegroups.com
By 3D vector I mean a vector of vectors of vectors (of unsigned chars, in my case).


This method is working—sort of.
Now I am just returning a (1D) vector of unsigned chars (I'll reshape it once it's a NumPy array in Python). The unsigned chars are all either 0 or 1 in the C++, but when I get the NumPy array with the method above, I find that some of the entries are what look like random values.

It sort of seems that the underlying data of the vector is somehow being partially overwritten by the time I look at it in Python. Is there something wrong with the refcounts, possibly?

Anyway, I'm a bit out of my league here, so if there is a canonical method for doing this, I'd much rather use that.

Sturla Molden

unread,
Jun 12, 2015, 7:24:38 AM6/12/15
to cython...@googlegroups.com
On 12/06/15 02:35, Will Mayner wrote:

> By 3D vector I mean a vector of vectors of vectors (of unsigned chars,
> in my case).

That is a complicated situation, because the underlying buffer of such a
"3D array" only has regular strides along the last dimension. Hence you
cannot create a NumPy array from it without copying the data. Another
problem is that the destructor of std::vector will deallocate the
buffer, so you need to prevent that as well. You could try to use an
Allocator object to ensure that the whole "3D buffer" has a regular
layout, as well as preventing deallocation in the std::vector destructor.

If you use a single std::vector as buffer you avoid the problem with
strides, as the whole buffer is now contiguous. But you still need to
prevent the deallocation. You could do that by defining an Allocator.
Another method would be to create a Cython cdef class which exports an
__array_interface__ to NumPy and owns the C++ std::vector. Then call del
on it in __dealloc__.



Sturla

Sturla Molden

unread,
Jun 12, 2015, 8:26:29 AM6/12/15
to cython...@googlegroups.com
On 12/06/15 13:24, Sturla Molden wrote:

> If you use a single std::vector as buffer you avoid the problem with
> strides, as the whole buffer is now contiguous. But you still need to
> prevent the deallocation. You could do that by defining an Allocator.
> Another method would be to create a Cython cdef class which exports an
> __array_interface__ to NumPy and owns the C++ std::vector. Then call del
> on it in __dealloc__.


Unless I made a typo, this should work for an 1D array. If you have 3D
you need to reshape the numpy array and do whatever you need to do in
C++, e.g. make a class that interprets a std::vector as a 3D array.


// asvoid.h

template <class T>
inline void *asvoid(std::vector<T> *buf)
{
T& tmp = *buf;
return (void*)(&tmp[0]);
}

// foobar.h
extern
void foobar(std::vector<unsigned char>& v);


# whatever.pyx

from libcpp.vector cimport vector

import nupy as np
cimport numpy as cnp

cdef extern from "asvoid.h":
void *asvoid(vector[unsigned char] *buf)

cdef extern from "foobar.h":
void foobar(vector[unsigned char] tmp) # ignore the &

class stdvector_base:
pass

cdef class vector_wrapper:

cdef:
vector[unsigned char] *buf

def __cinit__(vector_wrapper self, n):
self.buf = NULL

def __init__(VectorWrapper self, cnp.intp_t n):
self.buf = new vector[unsigned char](n)

def __dealloc__(vector_wrapper self):
if self.buf != NULL:
del self.buf

def asarray(vector_wrapper self):
"""
Interpret the vector as np.ndarray without
copying the data.
"""
base = stdvector_base()
intbuf = <cnp.uintp_t> asvoid(self.buf)
dtype = np.dtype(np.uint8)
base.__array_interface__ = dict(
data = (intbuf, False),
descr = dtype.descr,
shape = (n,),
strides = (dtype.itemsize,),
typestr = dtype.str,
version = 3,
)
base.vector_wrapper = self
return np.asarray(base)

# and then you would do something like this

def example(n):
cdef vector_wrapper w
w = vector_wrapper(n)
foobar(w.buf[0])
return w.asarray()


It is important that we create the numpy array from the std::vector
after we are done working with it in C++ because it can reallocate
its data buffer.


Sturla





Sturla Molden

unread,
Jun 12, 2015, 8:30:08 AM6/12/15
to cython...@googlegroups.com
On 12/06/15 14:26, Sturla Molden wrote:

> def __init__(VectorWrapper self, cnp.intp_t n):
> self.buf = new vector[unsigned char](n)

Speaking of typo...

def __init__(vector_wrapper self, cnp.intp_t n):

Will Mayner

unread,
Jun 12, 2015, 12:02:49 PM6/12/15
to cython...@googlegroups.com
Great, this is super helpful. It's a variant of the method I've tried so far (described in my last post). Hopefully doing it this way will setup ownership properly—I'm pretty sure that the issue with my current method is the deallocation consideration you mentioned.

Thanks very much; I'll give this a shot and post back the results.

Will Mayner

unread,
Jun 12, 2015, 1:04:20 PM6/12/15
to cython...@googlegroups.com
Ok, so I've implemented it, but it won't compile. It's giving me the following errors:

animat/asvoid.hpp: In instantiation of ‘void* asvoid(std::vector<T>*) [with T = unsigned char]’:
animat/animat.cpp:1787:69:   required from here
animat/asvoid.hpp:8:15: error: invalid initialization of reference of type ‘unsigned char&’ from expression of type ‘std::vector<unsigned char>’
     T& tmp = *buf;
               ^
animat/asvoid.hpp:9:24: error: subscripted value is neither array nor pointer
     return (void*)(&tmp[0]);
                        ^
animat/asvoid.hpp: In function ‘void* asvoid(std::vector<T>*) [with T = unsigned char]’:
animat/asvoid.hpp:10:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^

Any idea what's wrong?


On Friday, June 12, 2015 at 7:30:08 AM UTC-5, Sturla Molden wrote:

Will Mayner

unread,
Jun 12, 2015, 1:14:21 PM6/12/15
to cython...@googlegroups.com
Think I found it:

// In asvoid.h...


T
& tmp = *buf;

// should be

std
::vector<T>& tmp = *buf;


That gets it to compile. I'll try actually using it now and hopefully it works!

Sturla Molden

unread,
Jun 12, 2015, 1:23:39 PM6/12/15
to cython...@googlegroups.com
On 12/06/15 19:14, Will Mayner wrote:

> T&tmp =*buf;
>
> // should be
>
> std::vector<T>&tmp =*buf;

Yes. Sorry.


Sturla


Sturla Molden

unread,
Jun 12, 2015, 1:32:59 PM6/12/15
to cython...@googlegroups.com

By the way in my C++ rewrite of scipy.spatial.cKDTree for SciPy 0.17 I
have done something similar in a couple of places:

https://github.com/sturlamolden/scipy/blob/cpp_ckdtree/scipy/spatial/ckdtree/ckdtree.pyx#L91

https://github.com/sturlamolden/scipy/blob/cpp_ckdtree/scipy/spatial/ckdtree/ckdtree.pyx#L157


Sturla


On 12/06/15 19:14, Will Mayner wrote:
> --
>
> ---
> You received this message because you are subscribed to the Google
> Groups "cython-users" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to cython-users...@googlegroups.com
> <mailto:cython-users...@googlegroups.com>.

Sturla Molden

unread,
Jun 12, 2015, 1:37:07 PM6/12/15
to cython...@googlegroups.com
On 12/06/15 19:32, Sturla Molden wrote:
>
> By the way in my C++ rewrite of scipy.spatial.cKDTree for SciPy 0.17 I
> have done something similar in a couple of places:
>
> https://github.com/sturlamolden/scipy/blob/cpp_ckdtree/scipy/spatial/ckdtree/ckdtree.pyx#L91

And I did not use __cinit__ here because Python will initialize the
struct containing the cdef class with zeros.

Sturla

Will Mayner

unread,
Jun 12, 2015, 4:46:32 PM6/12/15
to cython...@googlegroups.com
It's working!

Thank you so much.
Reply all
Reply to author
Forward
0 new messages