-
-
Notifications
You must be signed in to change notification settings - Fork 10.8k
MAINT: combine string ufuncs by passing on auxilliary data #25796
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
MAINT: combine string ufuncs by passing on auxilliary data #25796
Conversation
Bytes, hehe :). So the thing is the method object is completely opaque right now (which could hold static data). The other options would be to expand |
But how would I set the method inside, say, Also, for these purposes, it really should be extra data that goes with the specific loop, which makes Note that in the FFT additions, I similarly made use of |
So, we could add it to the Of course to some degree you could also prefer tagging it on to the ufunc. But than you need to extend the way the ufuncs store loops now (since ufuncs assume evertying is in the |
Not sure what is the best approach, but you are right that for this use case, something associated with the I saw that you had a note in numpy/numpy/_core/include/numpy/_dtype_api.h Lines 246 to 250 in f6f3e41
This refers to filling it if not |
You can set I.e. you can do:
Which would mean you move the ufunc inspection into |
That's possible, I guess, but feels wrong; Note that making this a bit more general may be useful: for |
I am very happy to add a slot to the Attaching things to the ufunc is plausible, but right now the ufunc has a map of In other words, I am very happy if we add it to the |
Yes, agreed, attached to the Looking at https://docs.python.org/3/c-api/type.html#c.PyType_Slot, typically a |
In almost all cases, I like functions, but I was meaning a
And I think at least the second just won't be the case (or isn't worthwhile). |
OK, so closer to the existing |
e051304
to
0373679
Compare
*out_transferdata = NULL; | ||
} | ||
else { | ||
static_auxdata_struct *transferdata = (static_auxdata_struct *)malloc(sizeof(static_auxdata_struct)); |
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 followed your example above, but defining a .clone
might actually be nicer. But, really, I'm just messing around...
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 now realize there is really no point creating this transferdata
here since in the loop I can just access context->method->aux_data
. Is that what you had in mind? If so, I'll remove these changes in array_method.*
...
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.
Right we shouldn't build an AuxData struct at all of course, and this second iteration is what what I had in mind. Either context->method->const_data
or context->const_data
, and if all we do is copy it, the first seems just as well.
(Moving it to the context
could be done to make it a true scratch space, but we can also just add scratch space to context
for that.)
I think we should rename aux_data
, though. Since there already is the other auxdata and it has a per ufunc run lifetime! I can see static_data
, const_data
or even using context
/ctx
again.
One thing I was mentioning (but let's defer that) is that I don't want the Method objects struct fully public, so on the public side it would make sense to move this high up, so we can decide to make parts public (I think it might be fully opaque right now).
@@ -482,20 +427,24 @@ minimum_strided_loop(PyArrayMethod_Context *context, char *const data[], | |||
const npy_packed_static_string *sin1 = (npy_packed_static_string *)in1; | |||
const npy_packed_static_string *sin2 = (npy_packed_static_string *)in2; | |||
npy_packed_static_string *sout = (npy_packed_static_string *)out; | |||
if (_compare(in1, in2, in1_descr, in2_descr) < 0) { | |||
int cmp = _compare(in1, in2, in1_descr, in2_descr); | |||
if (cmp == 0 && (in1 == out || in2 == out)) { |
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.
@ngoldbaum - this I think is actually a small bug in the current implementation: if the strings compare equal, then no copy should happen if either side is in-place.
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.
thanks for catching this!!
@seberg - I used this PR for a proof of concept of adding aux data - does it make any sense? Obviously, if it does, we should take it out of this PR... |
0373679
to
b36b2c3
Compare
I went ahead and did all functions that could be fairly easily factored together... About 1000 fewer lines of code. |
b36b2c3
to
41b5a5a
Compare
p.s. The overall diff of |
Awesome, thanks for taking this on! I will look this over next week. |
@lysnikolaou you probably want to look at this too |
@@ -55,6 +55,7 @@ typedef struct PyArrayMethodObject_tag { | |||
PyArrayMethod_StridedLoop *unaligned_strided_loop; | |||
PyArrayMethod_StridedLoop *unaligned_contiguous_loop; | |||
PyArrayMethod_StridedLoop *contiguous_indexed_loop; | |||
void *aux_data; |
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.
Beyond the fact that it should be renamed, I think, the below is only relevant for followup or maybe if splitting out.
Just FYI: I think I would be OK to move this up (maybe very high) and if we make things public, include the existing ones up to, and including, flags.
The "methods/functions" should not be public, in order to ensure we can deprecate and replace them (it would be nice to add an accessors for some so that a non-NumPy library could use our functions).
(We could also move down name
, I really added it for debugging only.)
After adding this, it could probably also simplify the legacy arraymethod as it currently (ab)uses aux-data for static data, effectively.
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.
OK, thanks! I now call it static_data
and moved it just beyond flags
. It is still on the method, which is most logical if we allow filling it via fill_array_method_from_slots
.
I can see the logic of something on context
itself, though. But maybe we need to think through a bit where exactly it will be useful...
By the way, I'm making the DType API public in another PR, so maybe wait on that one being merged for a final round on this PR because there will definitely be conflicts. |
97503ae
to
75b5242
Compare
OK, this seems to work and I'm fairly happy with it. A general question is whether the dtype C API addition should be split off, and, if so, a specific one whether it might still be made part of gh-25754 (if not, then some guidance on what other files to adjust would be welcome...). For the ufuncs themselves, the diff of the whole PR is basically unreviewable, but it's OK if done commit by commit. Happy to split it up in different PRs is that feels better. |
OK great, I'm hoping to have the dtype API PR done today so hopefully this won't take more than a couple days to sort out. Hope that's OK with you too @lysnikolaou! No such thing as a major release of a nearly 20 year old open source project without gnrly merge conflicts when we get down to the line! |
Yes, n many ways it is a compliment to all those before us that there are so few major merge conflicts! |
Right: For the record, I am happy with the addition, I am not 100% on how much of the I was mostly hoping to delay these things until Nathans PR is in (and making the descriptor int64 and hiding the fields is gnarly business, even if hopefully not all that bad for downstream). |
Sure! I'm ready to try this for fixed-length dtypes as well as soon as it's merged. |
I don't really want to deal with making the |
75b5242
to
83a50c2
Compare
83a50c2
to
5f24538
Compare
5f24538
to
86bdcc9
Compare
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.
OK, I rebased, and things seem to work.
Main query is whether I put the documentation in the right place, and whether we should say anything about where static_data
is exposed.
Ah, oops, we clearly were pushing at the same time, sorry! I like your suggestion - since I messed this up, let me just push from my branch... |
Sorry for force-pushing to this branch earlier, I thought you wanted me to resolve the merge conflicts |
You had offered that very kindly, but I thought since I had seen the docs going in, I could just do it myself - sorry to work at cross purposes! |
Passing on the method that should be used via a member function pointer.
86bdcc9
to
9c722db
Compare
OK, should be all OK now. Reminder that for review of the ufunc changes, it makes most sense to go commit by commit - the difference of the whole set is basically unintelligible. |
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.
Nice, always love to see -1000 LOC PRs. In my defense, sometimes when you're writing a big block of code like this it's easy to lose the forest for the trees. Thanks so much for adding the new slot in the API, it seems like an obvious nice way to avoid a ton of boilerplate. The commit-by-commit review was very straightforward. Also the tests passing strongly indicates this refactor didn't lead to any unintended behavior changes.
@@ -482,20 +427,24 @@ minimum_strided_loop(PyArrayMethod_Context *context, char *const data[], | |||
const npy_packed_static_string *sin1 = (npy_packed_static_string *)in1; | |||
const npy_packed_static_string *sin2 = (npy_packed_static_string *)in2; | |||
npy_packed_static_string *sout = (npy_packed_static_string *)out; | |||
if (_compare(in1, in2, in1_descr, in2_descr) < 0) { | |||
int cmp = _compare(in1, in2, in1_descr, in2_descr); | |||
if (cmp == 0 && (in1 == out || in2 == out)) { |
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.
thanks for catching this!!
|
||
|
||
template <IMPLEMENTED_UNARY_FUNCTIONS f, const char* function_name> | ||
typedef bool (Buffer<ENCODING::UTF8>::*utf8_buffer_method)(); |
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.
nice, this is much simpler with the typedef, i'm glad the functor wasn't necessary in the end
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.
This one took my by far the longest - mostly to realize that this method pointers are 16 bytes long. But, yes, once I had it, I was pleased with it!
Great! I hope that with this example, refactoring the other PRs will be straightforward. |
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.
This was surprisingly easy to review commit-by-commit, which really speaks volumes for this approach! Let's merge this and I'll try to do it for fixed-length strings as well.
If you are all happy, we should just put this in, thanks. |
This
is just a proof of concept to showshows how one can reduce the code by using loops for multiple functions.tryingEDIT: now all useful functions done.maximum
andminimum
Right now, it is rather inelegant since I do not know how to pass on arbitrary data in the new array method framework... (@seberg?), so instead I base it on the name of the function. That said, at least that name was immediately useful!EDIT: it changes the array method so that it allows passing on aux data; this should really be a different PR!cc @ngoldbaum
p.s. Shaves
150055000 bytes off the (enormous) _multiarray_umath.cpython-311-x86_64-linux-gnu.so, though that is with debug symbols, etc.