Kurt Smit wrote:
> Clarifying question:  In #177, it says,
>
> "Currently there's no way of creating efficient functions taking a
> buffer argument -- for starters cdef functions doesn't support
> declaring an argument as a buffer, and even if it did, it would mean a
> costly (in this context) buffer acquisition on every call."
>
> more precisely, should it say "There's no way of creating efficient
> functions taking a buffer argument /using the buffer syntax/..." since
> one could always just have the function take a Py_buffer(*) argument,
> right?

Sure :-) But if you go that route, you need to access it by manually
multiplying strides and calculating offsets etc.; and when I say "buffer
argument" I refer to "special Cython buffers" :-) Py_buffer is just a
normal struct seen from Cython's perspective.

> On Fri, Apr 24, 2009 at 3:38 AM, Dag Sverre Seljebotn
> <[email protected]> wrote:
>> So, you wanted to target contiguous buffer passing first. And I was
>> saying that fixing
>>
>> http://trac.cython.org/cython_trac/ticket/177
>>
>> would for the most part amount to what we need. Adding support for
>>
>>   cdef void myfunc(object[int, mode="c"] buf)
>>
>> which would have the C signature
>>
>>   void myfunc(Py_buffer* buf) // + a contract on int/contiguous
>>
>> and have the caller be responsible for getting the buffer, gets us far.
>> It would mean that you could do
>>
>>   myfunc(myobject)
>>
>> and it would turn into
>>
>>   Py_buffer buf = acquire buffer from myobject
>>   raise exception if buf is not contiguous
>>   myfunc(&buf)
>>   release buf
>
> Just stating my knowledge as it is right now -- for
>
> def func2(object[int, mode='c'] buf):
>     # body here
>
> The 'buf' is passed in as a python object and a stack Py_buffer is
> generated inside func2's body that acquires the buffer from buf.  So
> for 'def' functions the buffer is acquired/released within the
> function scope, while for 'cdef' functions it is acquired/released
> outside.  I'd imagine that cpdef functions would be outside, too.

You are right about def. There is not support for cdef or cpdef currently,
but what you say for cdef would be #177. cpdef would need to be
in-between: It generates a cdef function would would have the #177
behaviour, and a def wrapper which would acquire the buffer.

This is exactly the same as for e.g. "(c/cp)def func(int a)" currently BTW:
 - With def, conversion from Python object happen within function
 - With cdef, the caller has to convert prior to calling (or, it could
already have an int)
 - With cpdef, both of the above, as a wrapper def is created

>> I first thought that adding one component more would get us all the way:
>> Automatic copying into contiguous buffers. So assuming #177 is
>> implemented, one would then move on to having it instead turn into:
>>
>>   Py_buffer buf = acquire buffer from myobject
>>   if (buf is contiguous) {
>>     myfunc(&buf);
>>   } else {
>>     Py_buffer buf2 = make contiguous copy of buf
>>     myfunc(&buf2)
>>   }
>>   release buf
>>
>> However, there's a (big) problem here: What Python object does buf2
>> refer to?
>>
>> Using the one of "buf" would be too confusing as they point to different
>> memory areas. One solution is just setting it to None, perhaps. However
>> once #177 is solved one will expect to be able to do
>
> How hard would it be to have a contiguous -> strided (or indirect)
> buffer copy utility function in Cython, and this copy be triggered
> after the myfunc(&buf2) call?  The downside would be a 2 copy penalty
> for passing a strided (or indirect) object to the function, but it
> would then handle the problem above, right?  The programmer should use
> contiguous arrays to avoid the inefficiency.

This was just a mistake on my part in my psuedo-code, I should have copied
out as well. (And we likely need "in/inout/out" specifications for speed
to optionally drop one copy, but let us by all means deal with that
later.)

But the problem with there not being a Python object to fill in for buf2's
Py_buffer remains. The buffer protocol doesn't define any way of creating
a new buffer of the same Python object type -- i.e. we couldn't create a a
new NumPy ndarray in the right way without adding an additional protocol.
Passing buf's Python object on buf2's behalf would just be wrong as then
accessing the buffer through e.g. slices (which goes through Python layer,
at least currently) would access a different memory area than through item
indexing...

>> All of this makes me think about pushing the "new buffer syntax" a bit
>> harder and get it started on in your GSoC. With that,
>>
>>   cdef void myfunc(int[:] buf)
>>
>> could easily give non-surprising effects for #177 and copy-in/copy-out,
>> as
>> the Python object is not "part of the deal".
>
> Let me get this right:  the "int[:] buf" is syntactic sugar for a
> Py_buffer, whereas "object[int] buf" represents a Python object that
> conforms to the buffer protocol.  Access to the "int[:] buf" would be
> always done at C level (perhaps a conversion on the indices), whereas
> access to the "object[int] buf" is by default done through the Python
> API layer, unless the indices are c ints.  So dealing with just the

Yes! Glad you got it, the description was a bit short.

> Py_buffers Cython side avoids much of the mucking around with
> PyObjects, etc, and would be much more efficient, since a Py_buffer is
> effectively a souped-up C array (N-dimensions, strides, suboffsets,
> etc.)  The "int[:] buf" can always be converted into a memoryview
> object for use in the Python layer.
>
> So, in the example above with the new syntax:
>
> cdef void myfunc(int[:] buf)
>
> would have the C prototype
>
> void myfunc(Py_buffer *buf) // + contract on contiguous/int

This is a digression:

Actually I was planning on having int[:] mean strided, and perhaps
int[::1] or something like that mean contiguous. One could use the third
field for any kind of stride configuration:

 int[:,::1] - C-contiguous
 int[::1,:] - Fortran-contiguous
 int[:,:] - Strided

These would be magic short-hands for more explicit specifications. Some more:
 int[::strided, ::indirect] - Matrix stored as pointers to strided columns
 int[::full, ::1] - First index could use any scheme (if-test required to
see if suboffset is -1 or not), while second index is always contiguous.

It just means the syntax is extensible, we would start with only
supporting contiguous.

> A call within Cython code with a PyObject argument would require an
> explicit cast, wouldn't it?
>
> So one would have to do
>
> myfunc(<int[:]>myobject)

I think the conversion could be automatic -- on the grounds that it
currently is if "def myfunc(int foo)". But it is what happens under the
hood anyway.

> Which would generate:
>
> Py_buffer temp_buffer = acquire buffer from myobject
> /* everything else the same, without object reference worries */
>
> If the programmer wants to pass in a buffer and get something back, do
> in Cython:
>
> cdef int[:] pass_buf = <int[:]>myobject
> myfunc(pass_buf)
> # convert pass_buf to other object, use its contents, etc.

Ah OK now I see where you are heading with the explicit cast. No, I didn't
think of it like this. Py_buffer is just a reference to existing memory --
we can "fake" that memory being contiguous by copying for a moment, but
that shouldn't change semantics -- an "int[:]" variable is a reference,
not allocated memory.

This is very different from Fortran, but matches the way Python works
where everything is a reference.

I.e. you could also do

myfunc(myobject)
cdef int[:] buf = myobject

and buf would get the output of myfunc. And:

cdef int[:] a = ...
cdef int[:] b = a
b[3] = 2 # also shows up in a[3]

and so on.

Note that in general, we should support all cases for cdef functions:

cdef func(int[::full] buf) # or something like that

would never require copying as it would support all 1D buffers. But if you do

cdef func(int[::1] buf)

then the buffer is forced to be contiguous, if necesarry by copying in and
out.


>> Your GSoC would then Cython-side consist more or less of
>>
>> a) #177, with new syntax
>> b) A generic mechanism for automatic coercion between buffers of
>> different
>> modes. That is:
>>
>> cdef int[::contiguous] buf # new syntax for mode="c" w/o Python obj?
>> buf = some_object
>>
>> Here, if the buffer of some_object is not contiguous, a contiguous copy
>> will be made! And when releasing buf, it would be copied back.
>>
>> This would make the parts necesarry for Fortran support tremendously
>> useful elsewhere for what I believe will not be much extra effort.
>>
>> (Though I could help out with the parts of those not needed for Fortran
>> support in order to not derail your project.)
>
> I'll need to digest this a bit, but I like it.

Sure. Note that I corrected my b) point in a seperate mail.

Dag Sverre

_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev

Reply via email to