Issue 365 getitem none#366
Issue 365 getitem none#366abergeron merged 3 commits intoTheano:masterfrom kohr-h:issue-365__getitem_none
Conversation
|
Another remark: I changed the behavior of indexing with lists and sequences of lists that contain only integers from "same as tuple" to "raise exception". The reason is that in Numpy, this type of indexing is fundamentally different from indexing with tuples (or sequences containing slices or Ellipsis): >>> x = np.array([1, 2])
>>> x[[0, 1, 1, 0, 1]]
array([1, 2, 2, 1, 2])
x[[slice(None)]] # same as x[:]
array([1, 2]) |
| # Add remaining entries from sliced.shape if existing (happens | ||
| # for 1 index or less if ndim >= 2). | ||
| new_shape.extend(sliced.shape[i:]) | ||
| return sliced.reshape(new_shape) |
There was a problem hiding this comment.
Please avoid the reshape if there where no None. This is a performance issue.
There was a problem hiding this comment.
Ok, avoiding unnecessary reshaping now.
| # objects, not counting None entries and the Ellipsis itself | ||
| num_slcs = self.ga.nd - (len(key) - key.count(None) - 1) | ||
| fill_slices = (slice(None),) * num_slcs | ||
| key = key[:ell_idx] + fill_slices + key[ell_idx + 1:] |
There was a problem hiding this comment.
Moving the Ellipsis processing outside of __cgetitem__ breaks a[...] = 1 or similar things.
There was a problem hiding this comment.
Right, I wasn't expecting that it uses __cgetitem__. That needs to change then, I'll check.
| key = key[:ell_idx] + fill_slices + key[ell_idx + 1:] | ||
|
|
||
| # Remove the None entries for indexing | ||
| getitem_idcs = tuple(k for k in key if k is not None) |
There was a problem hiding this comment.
We might need a similar thing in the __setitem__ code (yes, numpy apparently just ignores the None for that case).
There was a problem hiding this comment.
I now use the view returned in __getitem__ to do the same thing as before, so no need for new code.
| # a[()], which simply is a view in Numpy. | ||
| if len(getitem_idcs) <= 1: | ||
| getitem_idcs = (getitem_idcs + | ||
| (slice(None),) * (self.ga.nd - len(getitem_idcs))) |
There was a problem hiding this comment.
Does a[0] = [1, 2, 3, 4] (where a is shape (3, 4)) still works after moving this here?
| # Slice into array, then reshape, accommodating for None entries in key | ||
| sliced = self.__cgetitem__(getitem_idcs) | ||
| new_shape = [] | ||
| i = 0 |
There was a problem hiding this comment.
Don't forget to add cdef unsigned int iat the top.
|
I'm ok with the behaviour change wrt indexing with lists. It's better to not behave slightly differently. |
|
|
||
| def __setitem__(self, idx, v): | ||
| cdef GpuArray tmp = self.__cgetitem__(idx) | ||
| cdef GpuArray tmp = self.__getitem__(idx) |
There was a problem hiding this comment.
Don't do that. The point of __cgetitem__ was to avoid python overhead here (which is significant).
There was a problem hiding this comment.
Hm, alright. Let me find another way.
Actually, I saw that there's the def __getitem__(self, idx):
# Assuming idx is a list or sequence of lists
idx_arr = np.asarray(idx, dtype=int)
if self.ndim == 1:
if idx_arr.ndim == 2:
# Given as single-element list of index list
flat_idx_arr = idx_arr[0]
else:
flat_idx_arr = idx_arr
else:
strides = np.concatenate(np.cumprod(self.shape[1:]), [1])
flat_idx_arr = np.sum(idx_arr * strides[:, None], axis=0)
return self.take1(flat_idx_arr)Is that easily doable? |
|
That would be doable, yes. But let's keep this for another PR. It might be better to write different kernels to handle the multi dimension advanced indexing though. That is somewhere on my todo list. Also, I would like to add support for setting elements with a list or multiple lists, which cannot use the current code. |
Sure, I was just wondering since I stumbled upon the function. A poor man's version with index flattening would be quite nice, better than nothing. On the other hand, as you say, |
|
So, old |
|
Before having this PR merged, I would have liked to get some profiling. @abergeron you did speed up with cgetitem of code that was slow down otherwise in the new back-end. Do you remember that use case? We could profile it again before and after this PR. There is much python code in getitem before cgetitem is called. This could slow down computation a lot. |
|
First, it's already been merged. Second, there is no python code. All the code is Cython and will get converted to C. The only issue is that if you call python methods that can't be converted you pay a performance penalty. I'm reasonably confident that this isn't the case here. |
I ran this manually plus the old getitem tests, works for me. You'll notice that I moved quite a bit of logic from
__cgetitem__to__getitem__simply because I am faster when I can do this kind of stuff in Python. If you feel this is gonna cost too much efficiency (which I cannot say for sure, but probably it's no issue), you may want to push it back to C. Although that would likely mean all of it.Closes #365