summaryrefslogtreecommitdiff
path: root/doc/ufuncs.rst.txt
blob: d628b3f95e5d873a1c7121825bd844778220387e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
BUFFERED General Ufunc explanation
==================================

.. note::

  This was implemented already, but the notes are kept here for historical
  and explanatory purposes.

We need to optimize the section of ufunc code that handles mixed-type
and misbehaved arrays.  In particular, we need to fix it so that items
are not copied into the buffer if they don't have to be.

Right now, all data is copied into the buffers (even scalars are copied
multiple times into the buffers even if they are not going to be cast).

Some benchmarks show that this results in a significant slow-down
(factor of 4) over similar numarray code.

The approach is therefore, to loop over the largest-dimension (just like
the NO_BUFFER) portion of the code.  All arrays will either have N or
1 in this last dimension (or their would be a mis-match error). The
buffer size is B.

If N <= B (and only if needed), we copy the entire last-dimension into
the buffer as fast as possible using the single-stride information.

Also we only copy into output arrays if needed as well (other-wise the
output arrays are used directly in the ufunc code).

Call the function using the appropriate strides information from all the input
arrays.  Only set the strides to the element-size for arrays that will be copied.

If N > B, then we have to do the above operation in a loop (with an extra loop
at the end with a different buffer size).

Both of these cases are handled with the following code::

   Compute N = quotient * B + remainder.
   quotient = N / B  # integer math
   (store quotient + 1) as the number of innerloops
   remainder = N % B # integer remainder

On the inner-dimension we will have (quotient + 1) loops where
the size of the inner function is B for all but the last when the niter size is
remainder.

So, the code looks very similar to NOBUFFER_LOOP except the inner loop is
replaced with::

  for(k=0; i<quotient+1; k++) {
      if (k==quotient+1) make itersize remainder size
      copy only needed items to buffer.
      swap input buffers if needed
      cast input buffers if needed
      call function()
      cast outputs in buffers if needed
      swap outputs in buffers if needed
      copy only needed items back to output arrays.
      update all data-pointers by strides*niter
  }


Reference counting for OBJECT arrays:

If there are object arrays involved then loop->obj gets set to 1.  Then there are two cases:

1) The loop function is an object loop:

   Inputs:
	    - castbuf starts as NULL and then gets filled with new references.
	    - function gets called and doesn't alter the reference count in castbuf
	    - on the next iteration (next value of k), the casting function will
	      DECREF what is present in castbuf already and place a new object.

	    - At the end of the inner loop (for loop over k), the final new-references
	      in castbuf must be DECREF'd.  If its a scalar then a single DECREF suffices
	      Otherwise, "bufsize" DECREF's are needed (unless there was only one
	      loop, then "remainder" DECREF's are needed).

   Outputs:
            - castbuf contains a new reference as the result of the function call.  This
	      gets converted to the type of interest and.  This new reference in castbuf
	      will be DECREF'd by later calls to the function.  Thus, only after the
	      inner most loop do we need to DECREF the remaining references in castbuf.

2) The loop function is of a different type:

   Inputs:

	    - The PyObject input is copied over to buffer which receives a "borrowed"
	      reference.  This reference is then used but not altered by the cast
	      call.   Nothing needs to be done.

   Outputs:

            - The buffer[i] memory receives the PyObject input after the cast.  This is
	      a new reference which will be "stolen" as it is copied over into memory.
	      The only problem is that what is presently in memory must be DECREF'd first.