Alternate C-only array protocol for speed?
It seems that people are worried about speed of the attribute-based array interface when using small arrays in C. Here's an alternative: Define some attribute (for now, call it __array_c__), which returns a CObject whose value (which you get with PyCObject_GetVoidPtr) would be a pointer to a struct describing the array. It would look something like typedef struct { int version; int nd; Py_LONG_LONG *shape; char typecode; Py_LONG_LONG *strides; Py_LONG_LONG offset; void *data; } SimpleCArray; (The order here follows that of the array interface spec; if somebody's got any comments on what mixing int's, Py_LONG_LONG, and char's in a struct does to the packing and potential alignment problems I'd like to know.) version is there as a sanity check: I'd say for this version it's something like 0xDECAF ('cause it's lightweight, see ;-). It's primarily a check that you've got the right thing (sinc CObjects are intrinsically opaque types). Then: - the array object guarantees that the data, etc. remains alive, probably by passing itself as the desc parameter to the CObject. The array data would have to stay at the same location and the same size while the reference is held. - typecode follows that of the __array_typestr__ attribute - shape and strides are pointers to arrays of at least nd elements. - this doesn't handle byteswapped as-is. Maybe a flags, or endian, attribute could be added. - you can still have the full attribute-base array interface (__array_strides__, etc.) to fall back on. If the typecode is 'V', you'll have to look at __array_descr__. Creating one from a Numeric PyArrayObject would go like this: PyObject *create_SimpleCArray(PyArrayObject *a) { SimpleCArray *ca = PyMem_New(SimpleCArray, 1); ca->version = 0xDECAF; ca->nd = a->nd; ca->shape = PyMem_New(Py_LONG_LONG, ca->nd); for (i = 0; i < ca->nd; i++) { ca->shape[i] = a->dimensions[i]; } ca->strides = PyMem_New(Py_LONG_LONG, ca->nd); for (i = 0; i < ca->nd; i++) { ca->strides[i] = a->strides[i]; } ca->offset = 0; ca->data = &my_data; Py_INCREF(a); PyObject *co = PyCObject_FromVoidPtrAndDesc(ca, a, free_numeric_simplecarray); return co; } where void free_numeric_simplecarray(SimpleCArray *ca, PyArrayObject *a) { PyMem_Free(ca->shape); PyMem_Free(ca->strides); PyMem_Free(ca); Py_DECREF(a); } Some points: - you have to keep the CObject around: destroying it will potentially destroy the array you're looking at. - I was thinking that maybe adding a PyObject *owner could make it easier to keep track of the owner; I'm not sure, as the descr argument in CObjects can easily play that role. - The creator of the SimpleCArray is free to add elements to the end (as long as they don't affect the padding/alignment of the previous ones: haven't thought about this). You could put the real owner of the array data there, for example (say, if it was wrapping a Blitz++ array). Or have a small _strides[30] array at the end, and strides would point to that (saving you a memory allocation). This simple C interface would, I think, alleviate much worries about speed for small arrays, and even for large arrays. -- |>|\/|< /--------------------------------------------------------------------------\ |David M. Cooke http://arbutus.physics.mcmaster.ca/dmc/ |cookedm@physics.mcmaster.ca
On Fri, 2005-04-08 at 04:21, David M. Cooke wrote:
It seems that people are worried about speed of the attribute-based array interface when using small arrays in C.
I was a little worried too, but think the array protocol idea is a good one in any case. Thinking about this, I'm wondering if what we used to do in early numarray (0.2) wouldn't work here. Our "consumer interface" / helper function looked more like this: int getSimpleCArray(PyObject *o, SimpleCArray *info); It basically just fills in the caller's SimpleCArray struct using information from o and returns 0, or -1 with an exception set if there's some problem. In numarray's SimpleCArray struct, the shape and strides arrays were fully allocated (i.e. Py_LONG_LONG shape[MAXDIM];) so the struct could be placed in an auto variable with nothing to free() later. In this interface, there is no implied getattr at all, since the helper function getSimpleCArray() can be made as smart (i.e. given knowledge about specific types) as people are motivated to make it. So, for a Numeric array or a numarray or a Numeric3 array, getSimpleCArray would presumably just copy from struct to struct, but for other types, it might fall back on the many-getattr approach. Regards, Todd
Here's an alternative: Define some attribute (for now, call it __array_c__), which returns a CObject whose value (which you get with PyCObject_GetVoidPtr) would be a pointer to a struct describing the array. It would look something like
typedef struct { int version; int nd; Py_LONG_LONG *shape; char typecode; Py_LONG_LONG *strides; Py_LONG_LONG offset; void *data; } SimpleCArray;
(The order here follows that of the array interface spec; if somebody's got any comments on what mixing int's, Py_LONG_LONG, and char's in a struct does to the packing and potential alignment problems I'd like to know.)
version is there as a sanity check: I'd say for this version it's something like 0xDECAF ('cause it's lightweight, see ;-). It's primarily a check that you've got the right thing (sinc CObjects are intrinsically opaque types).
Then: - the array object guarantees that the data, etc. remains alive, probably by passing itself as the desc parameter to the CObject. The array data would have to stay at the same location and the same size while the reference is held.
- typecode follows that of the __array_typestr__ attribute
- shape and strides are pointers to arrays of at least nd elements.
- this doesn't handle byteswapped as-is. Maybe a flags, or endian, attribute could be added.
- you can still have the full attribute-base array interface (__array_strides__, etc.) to fall back on. If the typecode is 'V', you'll have to look at __array_descr__.
Creating one from a Numeric PyArrayObject would go like this:
PyObject *create_SimpleCArray(PyArrayObject *a) { SimpleCArray *ca = PyMem_New(SimpleCArray, 1); ca->version = 0xDECAF; ca->nd = a->nd; ca->shape = PyMem_New(Py_LONG_LONG, ca->nd); for (i = 0; i < ca->nd; i++) { ca->shape[i] = a->dimensions[i]; } ca->strides = PyMem_New(Py_LONG_LONG, ca->nd); for (i = 0; i < ca->nd; i++) { ca->strides[i] = a->strides[i]; } ca->offset = 0; ca->data = &my_data;
Py_INCREF(a); PyObject *co = PyCObject_FromVoidPtrAndDesc(ca, a, free_numeric_simplecarray); return co; }
where void free_numeric_simplecarray(SimpleCArray *ca, PyArrayObject *a) { PyMem_Free(ca->shape); PyMem_Free(ca->strides); PyMem_Free(ca); Py_DECREF(a); }
Some points: - you have to keep the CObject around: destroying it will potentially destroy the array you're looking at. - I was thinking that maybe adding a PyObject *owner could make it easier to keep track of the owner; I'm not sure, as the descr argument in CObjects can easily play that role. - The creator of the SimpleCArray is free to add elements to the end (as long as they don't affect the padding/alignment of the previous ones: haven't thought about this). You could put the real owner of the array data there, for example (say, if it was wrapping a Blitz++ array). Or have a small _strides[30] array at the end, and strides would point to that (saving you a memory allocation).
This simple C interface would, I think, alleviate much worries about speed for small arrays, and even for large arrays. --
David M. Cooke wrote:
It seems that people are worried about speed of the attribute-based array interface when using small arrays in C.
I think we are talking about here an *array protocol* (i.e. like the buffer protocol and sequence protocol). So far we have just described the Python level interface. I would like to see an array protocol added (perhaps to the buffer protocol table). This could be done just as David describes --- we don't even need to use the C-pointer (just return a void *pointer which has a version as the first entry). I think this is how the C-level should be handled, I think. Yes, it does not require changes to Python to implement the __array_c__ attribute. But, ultimately, it would be better if we used the C-level protocol concept that Python already uses for other objects. -Travis
Travis Oliphant <oliphant@ee.byu.edu> writes:
David M. Cooke wrote:
It seems that people are worried about speed of the attribute-based array interface when using small arrays in C.
I think we are talking about here an *array protocol* (i.e. like the buffer protocol and sequence protocol).
So far we have just described the Python level interface. I would like to see an array protocol added (perhaps to the buffer protocol table). This could be done just as David describes --- we don't even need to use the C-pointer (just return a void *pointer which has a version as the first entry).
The purpose of the CObject was to make it possible to pass it through Python (through the attribute access).
I think this is how the C-level should be handled, I think. Yes, it does not require changes to Python to implement the __array_c__ attribute. But, ultimately, it would be better if we used the C-level protocol concept that Python already uses for other objects.
Ah, ok, so you'd have a slot in the type object (like the number, sequence, or buffer protocols), with the appropriate (C-level) functions. This would require it to be in the Python core, though, and would only work for a new version of Python. Alternatively, you have a special attribute/method that returns an object with the right C API -- much like CObjects are used for wrapping Numeric's C API. I would really like to see something working at the C level (so you're not passing dimensions back-and-forth as Python tuples with Python ints), but the Python-level array interface you've proposed will work for now. This should be revisited once people are using the new array interface, and we have an idea of how it's being used, and the performance costs. -- |>|\/|< /--------------------------------------------------------------------------\ |David M. Cooke http://arbutus.physics.mcmaster.ca/dmc/ |cookedm@physics.mcmaster.ca
participants (4)
-
cookedm@physics.mcmaster.ca -
David M. Cooke -
Todd Miller -
Travis Oliphant