-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
gh-131798: JIT: Further optimize _CALL_ISINSTANCE
for class tuples
#134543
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
base: main
Are you sure you want to change the base?
Conversation
_CALL_ISINSTANCE
for class tuples_CALL_ISINSTANCE
for class tuples
self.assertIn("_BUILD_TUPLE", uops) | ||
self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) |
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.
_BUILD_TUPLE
is preventing us from optimizing out _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW
.
The bytecode is basically:
LOAD_CONST
LOAD_CONST
_BUILD_TUPLE
_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW
To optimize this, we'd need some special handling for _BUILD_TUPLE
in remove_unneeded_uops
.
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.
Got it, might be worth looking into next if you're up for it! Could be tricky, though.
When you're done making the requested changes, leave the comment: |
So that was a lie 😆 Anyway, I think I addressed all your comments! :) I have made the requested changes; please review again |
Thanks for making the requested changes! @brandtbucher: please review the changes made to this pull request. |
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 good, just a few suggestions:
self.assertIn("_BUILD_TUPLE", uops) | ||
self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) |
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.
Got it, might be worth looking into next if you're up for it! Could be tricky, though.
for _ in range(n): | ||
# One of the classes is unknown, so we can't narrow | ||
# to True or False, only bool | ||
y = isinstance(42, (eval('str'), int)) |
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.
I'm confused, why can't we narrow to True
? We can't remove the call, but the result is known.
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.
Yep, that was a brainfart 😄 I somehow conflated narrowing to True/False with replacing the op, but we can obviously just narrow without removing the call as you said!
def testfunc(n): | ||
x = 0 | ||
for _ in range(n): | ||
y = isinstance(42, ()) |
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.
I had to pause and think for a second to figure out what this would even do. Nice edge case.
Python/optimizer_bytecodes.c
Outdated
// It could potentially define its own __instancecheck__ | ||
// method so we can only deduce bool. | ||
out = NULL; | ||
break; |
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 this be continue
? We could still narrow to True
later to handle cases like the one I called out above, right?
break; | |
continue; |
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.
Yup, see #134543 (comment). I just needed to add some extra bookkeeping to know when we can replace the call and when not.
} | ||
PyTypeObject *cls_o = (PyTypeObject *)sym_get_const(ctx, item); | ||
if (cls_o && | ||
sym_matches_type(item, &PyType_Type) && |
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.
Maybe add a comment explaining that this is to protect against metaclasses definine __instancecheck__
.
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.
How would you formulate it? I don't think of it as specifically a guard for __instancecheck__
but basically PyObject_TypeCheck
adapted to the JIT optimizer.
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.
We're not only checking that the object is a subclass of type
, we're also checking that it is an exact instance of type
itself. We care about this second condition because it guarantees that __instancecheck__
doesn't exist (otherwise we would need to look it up to check if it exists or not).
Off-topic, but something I've been thinking about that you may want to check out next: removing bounds checks when indexing into strings and tuples with a constant index. |
We can already const eval
isinstance(inst, cls)
calls when both arguments are known, but only ifcls
is a single class (e.g.int
).This PR adds support for
isinstance(inst, (cls1, cls2, ..., clsN))
. This allows us to optimize for example:isinstance(42, (int, str))
(toTrue
)isinstance(42, (bool, str))
(toFalse
)We can narrow to
True
even when only some of the classes are known, as long asinst
is an instance of one of the known classes.