-
-
Notifications
You must be signed in to change notification settings - Fork 9.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BUG: Fix iteration over reversed subspaces in mapiter_@name@ #8284
Conversation
@@ -1707,10 +1712,22 @@ mapiter_@name@(PyArrayMapIterObject *mit) | |||
} | |||
|
|||
/* | |||
* Resetting is slow, so skip if the subspace iteration has | |||
* only a single inner loop. | |||
* Resetting is slow, so skip if the subspace iteration is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@seberg I am not sure if the prediction works correctly for higher dimensional subspace.
Saying the fastest dimension has strides == +-itemsize, but the last-fastest dimension doesn't. Not sure if I can quickly come up with a test case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me try to explain nditer a bit, I think it will make things clear why it should work. I am not sure it might help but...
The advanced iterator will always do some "iteration", after each of these there is an inner loop optimization. This inner loop can be iterated trivially by a single stride. Now in principle the inner stride you need can change of course.
This is why there is the check that the innerloop is actually the complete iteration. If the innerloop spans the whole iteration there is no reason for the innermost stride to change later on (assuming no buffering, buffering might add weirdness). The only reason why this could happen is if the strided layout is different for different "subspaces/inner iteration positions", but that is not compatible to numpy's memory layout.
All in all, even if you change the iterator to do a different iteration order, these things should still work as far as I can tell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I get your point. It sounds like the subspace is compatible to 1d -- I mean .ravel() can returns a reference not a copy. Is there a name in numpy for this property? ravellable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, precisely. And no, I don't know a nice word for that property, since its not contiguous....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want to give it a name to properly refer to it. I see code appears to be dealing with this in numpy.reshape too.
Linking the PR to #8264 |
2946106
to
2b5d473
Compare
@@ -1724,6 +1741,10 @@ mapiter_@name@(PyArrayMapIterObject *mit) | |||
NPY_AUXDATA_FREE(transferdata); | |||
return -1; | |||
} | |||
if(subspace_ptrs[0] == self_ptr && | |||
subspace_ptrs[1] == mit->extra_op_ptrs[0]) { | |||
skip <<= 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why two iterations without checking closer, but can't the bit shift overflow to 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the second part of your question: shifting stops after skip == 4 -- if skip started with 1. Otherwise skip started with 0 and shifting never do anything. So it won't overflow.
The first part of your question I don't really understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
skip = 1(fit the offset), 2 (twice, fit the stride), 4(the linear model is fitted, use predictions from this point on)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still don't get it. Why stride? This is purely an offset in the subspace (from the point of view of the outer iteration), the inner stride is already known? In fact, even if they were different (and skip was 1) you could save the difference and just apply it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right. I changed it to 1 and all tests passes too. The offset from selfptr and the first iteration is always a constant, and we can calculate it on the first iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. It seems that defining reset_offsets
(or maybe we can even think of a new name) is pretty understandable. It would be cool if we can make the comments a bit clearer, I tried to suggest/drop some words, maybe you can think of something nicer too.
@@ -1632,7 +1632,8 @@ mapiter_@name@(PyArrayMapIterObject *mit) | |||
char *subspace_baseptrs[2]; | |||
char **subspace_ptrs = mit->subspace_ptrs; | |||
npy_intp *subspace_strides = mit->subspace_strides; | |||
int skip = 0; | |||
int is_reset_trivial = 1; | |||
npy_intp reset_offsets[2]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be initialized to avoid compile time warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it proper numpy C to initialize with {}?
if (*counter != PyArray_SIZE(mit->subspace)) { | ||
/* | ||
* if the subspace iterator skips, we cannot avoid resetting | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remembered the word, its "trivially iterable" (at least within much of our code). Just trying to make it a bit clearer (only some thougths): If the subspace iterator is not trivially iterable (so not in a single stride), it has to be reset to the correct start point in every outer iteration. If it is trivially iterable we can avoid using it alltogether (the actual loop does nothing).
* because the internal iteration of each external iterations, | ||
* share the same structure, if we are correct once we know | ||
* future iterations we are always correct. | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for extra blank line. The comment is outdated with this version of the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
reset_offsets[0] = subspace_ptrs[0] - self_ptr; | ||
reset_offsets[1] = subspace_ptrs[1] - mit->extra_op_ptrs[0]; | ||
/* use the faster adjustment */ | ||
is_reset_trivial ++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: I wouldn't put the space, though I guess it really does not matter, nor am I sure its part of any style guideline.
subspace_ptrs[0] = self_ptr; | ||
subspace_ptrs[1] = mit->extra_op_ptrs[0]; | ||
/* | ||
* will avoid resetting if the reset is trival. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly could try to expand this a little along the same lines as above, especially an example might help. If the inner/subspace iterator is trivially iterable, we still may need to calculate the correct starting point of the iteration. The offset is typically zero unless reversing the iteration is more efficient (originally a negative stride) in reversed order in which case this is the offset to the last item.
@@ -497,6 +497,14 @@ def test_indexing_array_weird_strides(self): | |||
zind = np.zeros(4, dtype=np.intp) | |||
assert_array_equal(x2[ind, zind], x2[ind.copy(), zind]) | |||
|
|||
def test_indexing_array_weird_strides_8264(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not matter, but maybe negative strides explicitly is nicer ;).
Thanks, looks all good. If you can squash it, I will put this in. |
c76044d
to
4cf14dc
Compare
squashed. |
The summary line could be improved, something like
Then put the |
Also more explanation of what the problem was, e.g., visible failure. Currently is all "what" and no "why". |
As stated in numpy#8264, before this patch numpy crashes when the subspace of iterator has negative strides on the faster resetting branch for trivially iterable subspaces in mapiter_@name@. Noticing the offset between ptr and first item in subspace is constant, we calculate the offset from the first iteration and use it onwards. Fixes numpy#8264
4cf14dc
to
cce86d6
Compare
Indeed. Poorly written. How does it look now? On Mon, Nov 21, 2016 at 12:31 PM, Charles Harris notifications@github.com
|
LGTM. I'll let @seberg finish it up. |
LGTM, thanks a lot @rainwoodman. |
my pleasure! |
This PR will fix 8264. It is a WIP.