Skip to content
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-44946: Streamline operators and creation of ints for common case of single 'digit'. #27832

Merged
merged 17 commits into from Aug 25, 2021

Conversation

Projects
None yet
5 participants
@markshannon
Copy link
Contributor

@markshannon markshannon commented Aug 19, 2021

Modest speedup of 1%

The speedup is unexciting, although every little helps.
However, this will help with specialization of integer operations, as that will remove additional overhead.

The obvious other speedup of using a freelist is left for another PR, as we need to rationalize our use of freelists before adding more.

Skipping NEWS as there is no change to any APIs and the performance increase is marginal.

https://bugs.python.org/issue44946

Objects/longobject.c Outdated Show resolved Hide resolved
Objects/longobject.c Show resolved Hide resolved
Objects/longobject.c Outdated Show resolved Hide resolved
Objects/longobject.c Outdated Show resolved Hide resolved
Objects/longobject.c Outdated Show resolved Hide resolved
@mdickinson mdickinson self-assigned this Aug 19, 2021
@mdickinson mdickinson removed their assignment Aug 19, 2021
@mdickinson mdickinson self-requested a review Aug 19, 2021
Objects/longobject.c Outdated Show resolved Hide resolved
Objects/longobject.c Outdated Show resolved Hide resolved
Objects/longobject.c Outdated Show resolved Hide resolved
Objects/longobject.c Outdated Show resolved Hide resolved
Objects/longobject.c Outdated Show resolved Hide resolved
Objects/longobject.c Outdated Show resolved Hide resolved
@mdickinson
Copy link
Member

@mdickinson mdickinson commented Aug 20, 2021

@markshannon Sorry, I started reviewing while you were still committing; please could you ping me when the PR is stable and ready for review?

@markshannon
Copy link
Contributor Author

@markshannon markshannon commented Aug 20, 2021

@mdickinson All done and ready for review.
(Not quite, still need to fix some complaints from the MSVC compiler)

@markshannon
Copy link
Contributor Author

@markshannon markshannon commented Aug 20, 2021

@mdickinson All done and ready for review (for real this time).

There were three things that conspired to make this rather more work than I had anticipated.

  • We uselong everywhere, even though it differs in size between Windows and other 64 bit platforms.
  • That thesdigit type has two spare bits on 64 bit machines (32 to 30) but only one on 32 bit platforms meaning that digit op digit needs more than sdigit space on 32 bit machines, even though it is fine on 64 bit machines.
  • GCC does not give warnings for dubious implicit casts. Fortunately MSVC does.

The end result was an infuriating amount of debugging via CI.

@mdickinson
Copy link
Member

@mdickinson mdickinson commented Aug 21, 2021

  • We uselong everywhere, even though it differs in size between Windows and other 64 bit platforms.

Yes, we really shouldn't: everything that's working exclusively with PyLong digits should be using one of the dedicated types digit, sdigit, twodigits or stwodigits.

@mdickinson mdickinson self-requested a review Aug 21, 2021
Copy link
Member

@mdickinson mdickinson left a comment

A few comments. The main one: please can we restore the old version of PyLong_FromLong? As much as possible, I'd like to keep the digit-based logic (which should be using nothing other than digit, sdigit, twodigit and stwodigits to represent values) separate from the logic that has to deal with arbitrary C types; tangling them up would make it harder to change the representation later. (E.g., if 128-bit integers become widely supported, it may still make sense to look into 60-bit digits at some point.)


#define IS_SMALL_INT(ival) (-NSMALLNEGINTS <= (ival) && (ival) < NSMALLPOSINTS)
#define IS_SMALL_UINT(ival) ((ival) < NSMALLPOSINTS)

#define IS_MEDIUM_INT(x) (((twodigits)x)+PyLong_MASK <= 2*PyLong_MASK)
Copy link
Member

@mdickinson mdickinson Aug 21, 2021

It would be useful to have a comment clarifying what range of values this macro can safely be used for. I'm assuming it should be enough that it's valid for values in the range (-PyLong_BASE**2, PyLong_BASE**2).

Copy link
Member

@mdickinson mdickinson Aug 25, 2021

Sorry, I think I was unclear. The (twodigits)x cast potentially loses information if x is large enough, leading to the possibility of false positives for IS_MEDIUM_INT. For example, that will happen on Windows with a large Py_ssize_t value and 15-bit digits - in that case, Py_ssize_t is much larger than unsigned long.

So there's some restriction on the value of x for which this test is valid. "Fits in stwodigits" would probably be enough, but I don't think we use this macro for values outside the range (-PyLong_BASE**2, PyLong_BASE**2).

Copy link
Member

@mdickinson mdickinson Aug 25, 2021

More generally, C's integer-handling rules make this sort of thing horribly messy to reason about: for example in the 15-bit digit case the addition is an addition of an unsigned long to a (signed!) int, since the integer promotions will promote the unsigned short PyLong_MASK to an int (though even that part is not guaranteed by the standard - there's nothing preventing short and int having the same precision, in which case PyLong_MASK will be promoted to unsigned int instead of int). So now we have to consult the rules for unsigned + signed addition in the "usual arithmetic conversions", which eventually say that because long has greater rank than int (even if it has the same precision), both operands will be treated as unsigned long for the addition.

The 2 * PyLong_MASK is another case that could end up being either signed or unsigned depending on ranks, types, etc; it's probably better spelled as 2U * PyLong_MASK; that way we can at least be sure that it's performed as an unsigned multiplication and that the final comparison is unsigned-to-unsigned.

I'd suggest the addition of an extra cast around the result of the addition, just to reduce the number of mental hoops one has to jump through to establish that this really does give the right result: that is,

((twodigits)((twodigits)(x)+PyLong_MASK) <= 2U*PyLong_MASK)

We should also add extra parentheses around the x, in case someone tries to use IS_MEDIUM_INT on an expression more complicated than a single name.

Copy link
Contributor Author

@markshannon markshannon Aug 25, 2021

I wholeheartedly agree that C's integer handling is a pain to think about 😞

For clarity I think this is best to use an inline function that makes all casts super explicit.
That way that it makes the cast explicit (if called with something other than stwodigits or sdigits, the caller is responsible.

static inline int is_medium_int(stwodigits x)
{
    /* We have to take care here to make sure that we are
     * comparing unsigned values. */
    twodigits x_plus_mask = ((twodigits)x) + PyLong_MASK;
    return x_plus_mask < ((twodigits)PyLong_MASK) + PyLong_BASE;
}

Does that seem sensible?

Objects/longobject.c Outdated Show resolved Hide resolved
Objects/longobject.c Show resolved Hide resolved
Objects/longobject.c Show resolved Hide resolved
Objects/longobject.c Outdated Show resolved Hide resolved
@mdickinson
Copy link
Member

@mdickinson mdickinson commented Aug 21, 2021

One other thing: please could you post your benchmark methodology and results, either here or on the issue? (Probably more appropriate to post on the issue.) I'd like to see if I can reproduce the speedup you're reporting.

@markshannon
Copy link
Contributor Author

@markshannon markshannon commented Aug 23, 2021

Latest benchmarks
Using full release builds (PGO and LTO).

@mdickinson
Copy link
Member

@mdickinson mdickinson commented Aug 23, 2021

Thanks for all the updates! I'll make another (final, I hope) review pass shortly.

@bedevere-bot
Copy link

@bedevere-bot bedevere-bot commented Aug 23, 2021

🤖 New build scheduled with the buildbot fleet by @mdickinson for commit 1f2d47c 🤖

If you want to schedule another build, you need to add the "🔨 test-with-buildbots" label again.

@bedevere-bot
Copy link

@bedevere-bot bedevere-bot commented Aug 23, 2021

🤖 New build scheduled with the buildbot fleet by @markshannon for commit 649c311 🤖

If you want to schedule another build, you need to add the "🔨 test-with-buildbots" label again.

@markshannon
Copy link
Contributor Author

@markshannon markshannon commented Aug 24, 2021

The failure on buildbot/AMD64 Arch Linux Asan Debug PR is unrelated.

Copy link
Member

@mdickinson mdickinson left a comment

LGTM modulo the IS_MEDIUM_INT definition changes (parentheses around the x, 2U in place of 2).

Objects/longobject.c Outdated Show resolved Hide resolved
@markshannon markshannon merged commit 15d50d7 into python:main Aug 25, 2021
11 checks passed
@markshannon markshannon deleted the streamline-medium-ints branch Sep 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment