-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
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
bpo-31031: Unify duplicate bits_in_digit and bit_length #2866
bpo-31031: Unify duplicate bits_in_digit and bit_length #2866
Conversation
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.
check the build errors plz
ccdbe59
to
900f7a8
Compare
Include/pymath.h
Outdated
* Equivalent to floor(lg(x))+1. Also equivalent to: bitwidth_of_type - | ||
* count_leading_zero_bits(x) | ||
*/ | ||
extern unsigned int _Py_bit_length(unsigned long d); |
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.
You should use PyAPI_FUNC(unsigned int) rather than extern.
By the way, _Py symbols should be moved to a new Include/cython/pymath.h header IMHO, and excluded from the stable API.
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.
(1) Done.
(2) Sounds reasonable. Would you prefer to start this new file now (and move the other private functions later), or a seperate patch that moves all of the existing private functions including this new one?
|
||
unsigned int _Py_bit_length(unsigned long d) { | ||
unsigned int d_bits = 0; | ||
while (d >= 32) { |
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.
Can't we unroll manually the loop using SIZEOF_LONG? For example, 4 bytes long need 0 loop. 8 bytes long only require a single if(). There is no platform supported by Python with long larger than 8 bytes (64 bits).
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.
A 4-byte long would need up to 6 iterations (and an 8-byte long up to 11): we're only removing 6 bits per 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.
This can probably be optimized to work without branching (using the __clz
intrinsic or equivalents). But I'd prefer to keep this patch as a pure refactoring, simply chosing the better of the existing implementations, for now.
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.
Oh wait, I didn't notice that it's "only" 6 bits per iteration. I read 32 bits by mistake. You can ignore my comment.
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.
Using clz was discussed in: https://bugs.python.org/issue29782
But it was rejected: https://bugs.python.org/issue29782#msg290973
Maybe the best we can do is to add a short comment to explain why we use a "naive" implementation (not really naive, it uses a precomputed table) linking to bpo-29782.
Would you mind to add such short comment?
By the way, int.bit_length() has a complexity of compleO(1) in practice, no? (I consider that this function has a complexity of O(1), especially when you consider Python int objects made of many "Python digits".)
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
f225c8e
to
dcaa962
Compare
Thanks for reviewing. I have made the requested changes; please review again. |
Thanks for making the requested changes! @vstinner: please review the changes made to this pull request. |
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 | ||
}; | ||
|
||
unsigned int _Py_bit_length(unsigned long d) { |
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.
Would it make sense to convert this function to an static inline function only exposed in the internal C API?
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 sure, the declaration in pymath.h
should be protected by an #ifndef Py_LIMITED_API
. @vstinner: I'm not sure I'm understanding you correctly, though. Making this static
would make it unavailable to the other files that need it, wouldn't 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.
Ah, I see: you're suggesting moving the entire function definition to pymath.h
, and making it static inline
. Is that right?
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. Sorry, my comment was unclear. I converted some macros to static inline functions in header files.
I don't know if it would be worth it in term of performance.
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.
Could be worth a try; I'd be surprised if it makes a measurable difference, but we won't know without testing.
But if not, we do need the Py_LIMITED_API
guard: we don't want to expose _Py_bit_length
to 3rd party libraries.
dcaa962
to
7b4198c
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.
LGTM.
Thanks @niklasf, sorry for slow reviews and for being so pedantic :-) But it was worth it, I like the final change ;-) I looked again at rejected https://bugs.python.org/issue29782 and #594 I was going to ask further change on this PR to prepare the code in case if someone wants to experiment again more efficient functions... But nah, the code is ok and can be updated later. |
Add _Py_bit_length() to unify duplicate bits_in_digit() and bit_length().
In pythonGH-2866, _Py_Bit_Length() was added to pymath.h for lack of a better location. pythonGH-20518 added a more appropriate header file for bit utilities. It also shows how to properly use intrinsics. This allows reconsidering bpo-29782. * Move the function to the new header. * Changed return type to match __builtin_clzl and reviewed usage. * Use intrinsics where available. * Pick a (mostly theoretical) fallback implementation suitable for inlining.
In GH-2866, _Py_Bit_Length() was added to pymath.h for lack of a better location. GH-20518 added a more appropriate header file for bit utilities. It also shows how to properly use intrinsics. This allows reconsidering bpo-29782. * Move the function to the new header. * Changed return type to match __builtin_clzl() and reviewed usage. * Use intrinsics where available. * Pick a fallback implementation suitable for inlining.
In pythonGH-2866, _Py_Bit_Length() was added to pymath.h for lack of a better location. pythonGH-20518 added a more appropriate header file for bit utilities. It also shows how to properly use intrinsics. This allows reconsidering bpo-29782. * Move the function to the new header. * Changed return type to match __builtin_clzl() and reviewed usage. * Use intrinsics where available. * Pick a fallback implementation suitable for inlining.
https://bugs.python.org/issue31031