-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
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
gh-118610: Centralize power caching in _pylong.py
#118611
Conversation
to cache the powers they need.
needed, and added asserts to ensure that's so.
newer int->str function to receive fewer powers than it needed.
I've been testing roundtrip times (and verifying results) by running this: Code herefrom random import randrange
from time import perf_counter as now
import _pylong
bits = 500
while bits <= 10_000_000_000:
print("bits", format(bits, "_"))
hibit = 1 << (bits - 1)
for i in range(3):
lo = randrange(hibit)
n = lo + hibit
assert n.bit_length() == bits
_pylong.setold()
s = now()
nn = int(str(n))
f = now()
e1 = f - s
assert n == nn
_pylong.setnew()
s = now()
nn = int(str(n))
f = now()
e2 = f - s
assert n == nn
print(f" {e1:.3f} {e2:.3f} {e2/e1}")
bits += (bits >> 1) + randrange(-9, 10) An example of an unusually good bit length:
Under 1 means the new code is faster. So about 5% speedup there. And an example of an unusually bad bit lentgh:
So no apparent difference there. Typical:
So about 2% faster. Speed isn't the primary point of this PR, but it's good that it generally helps a bit. I'm not 100% sure, but I expect the best cases are ones where Toward that end, it's only the largest powers needed that really matter. Computing the very largest power needed can consume a significant fraction of the total conversion time. |
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.
If the goal is simplification of the code, I do not see how is this change an improvement. My implementation of int_to_decimal_string()
is about 20 lines long (ignoring comments), and the caching of 10**w
adds only 3 lines. w2pow()
and w5pow()
add 12 lines each. compute_powers()
is almost 50 lines long.
The primary goal, yes, but not the only. We'll have to disagree about whether it's simpler. As I noted when reviewing your code, the caching scheme you made up, while brief, was quite unlike the caching scheme the other two functions used (which were very similar to each other). That raises the bar for understanding the code as a whole. All caching logic is one place now. For the other two functions, their caching logic consumed a very large proportion of their total code.
|
BTW, even in your function, centralizing the caching logic reduces the body of its |
That was for running comparative timing tests. & that's over.
to be negative too, and check that the result string doesn't start with a zero.
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 correct to me.
When you finish with this, please do not forget to edit the commit message when merging the PR.
Spelling error. Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Serhiy Storchaka <[email protected]>
Last time I did "squash & merge", GitHub just went ahead and did it - it didn't give me a chance to edit. We'll see |
It happens. If for some reason GitHub fails to merge the first time, do not try to press the button again. First reload the page. |
Let me explain "simplify" a bit more. The new function obviously isn't "simpler". The comment before it ends with:
That's the point. The 3 (so far) client functions are inarguably simpler after the change, and that's the primary point. Base conversion is the primary job of this module, and the functions doing that are the focus. Computing powers of bases is a technical detail, that can be (and now is) delegated to an expert about that alone. "Separation of concerns." |
Do you think this should make it into 3.13, @tim-one? |
Not needed. The primary point was to simplify the internal coding. The only visible effect should be speedups of at most a few percent when doing giant int<->string conversions. I doubt that really matters much to anyone. |
) A new `compute_powers()` function computes all and only the powers of the base the various base-conversion functions need, as efficiently as reasonably possible (turns out that invoking `**`is needed at most once). This typically gives a few % speedup, but the primary point is to simplify the base-conversion functions, which no longer need their own, ad hoc, and less efficient power-caching schemes. Co-authored-by: Serhiy Storchaka <[email protected]>
A new internal function computes, in advance, all and only the powers various other functions will need. While a few percent faster overall (timing roundtrip
int(str(n))
on "large" n), the primary point is to free the other functions from needing to embed their own ad-hoc caching schemes.Still a work in progress. For now, both the old and new versions of the client functions are present, and new
setold()
andsetnew()
functions allow dynamically switching between them. That's for testing, and all that will eventually be removed._pylong.py
#118610