-
Notifications
You must be signed in to change notification settings - Fork 805
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
Use vectorcall (where possible) when calling Python functions #4456
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
f5aa30a
Use vectorcall (where possible) when calling Python functions
ChayimFriedman2 9a80eba
Add vectorcall benchmarks
ChayimFriedman2 2e310c2
Merge branch 'main' into call-vectorcall
davidhewitt 7b3872a
Fix Clippy (elide a lifetime)
ChayimFriedman2 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Improve performance of calls to Python by using the vectorcall calling convention where possible. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Rather than adding to this trait, we should look at its upcoming replacement
IntoPyObject
and consider how to slot these methods on there or a companion trait. We need to migrate theIntoPy<Py<PyTuple>>
bound on the.call
functions anyway, so this is a good time to bring this up cc @Icxolu.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 don't know how you expect this new trait(s) to look like, but it shouldn't be hard to migrate. I believe it is out of scope for this PR though.
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.
Also, I think
IntoPyObject
has a worse API (in some part): it cannot convert one Rust type to multiple Python type, which can especially hurt calls (for example, because it prevents supporting calling with arrays orVec
without an inefficient conversion).This has advantages - less type annotation, but I think these trait can coexist (with calls using
IntoPy
).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.
Hey, thanks for the ping David! I have to say upfront I'm not really familiar with these different calling conversions.
Together with fallibility I would considers the the two major advantages of the new API. During the experimentation phase we concluded that there is generally a clear Python target type for any Rust type. The additional complexity would make this overall less ergonomic to while bringing not much benefit in general.
IMO we should not keep
IntoPy
around. It has clear problems regarding fallible conversions. Also there should really be one trait responsible for converting Rust value into Python objects. Everything else is way harder to explain and to maintain. For example the implementations could get out of sync and the same value in Rust will be converted differently depending on which API it is given to. (This can already happen withToPyObject
andIntoPy
currently, and I think we should get rid of it and not introduce a new form here)If I understood correctly the problem is that we also want to convert arrays,
Vec
s, ... to aPyTuple
while there normally convert into aPyList
. I think we can support that special casing withIntoPyObject
as well, using another method that convertsSelf
into aPyTuple
"args" object. A quick sketch below with my limited understanding.If I got something wrong, or overlooked something, let me know, but in general I think it should be possible to support this with
IntoPyObject
as well.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 believe it is indeed possible to support this with
IntoPyObject
.If we are already making a breaking change, I think a better path than adding methods on
IntoPyObject
is to use another trait for calls, sayPyCallArgs
. This has the following advantage:PyCallArgs
, this will allow us to easily enable future possibilities, even ones that we cannot predict, around perf and not only.IntoPyObject
, you have to check you actually got a tuple. The overhead can be mitigated for known-tuples by specializing methods on them, but it is still not the best API since it does not prevent non-tuples at compile time and doesn't even signal the user their code is going to fail.Anyway, this is unrelated to this PR. We can land it now, and I expect any changes around calling can be adjusted fairly trivially.
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.
An additional reason I find the different trait approach tempting is that it can be used for both more convenient and more performant approach for kwargs, even without waiting for a
pycall!
macro - if we choose this path, we can instead of takingkwargs: Option<PyDict>
take generic type that can convert to a dict.For example,
That already means people can more nicely use kwargs with syntax like
call((arg1, arg2, ...), [("a", 1), ("b", 2), ...])
. But in addition, we may specialize the impls to instead of converting toPyDict
, using the vectorcall API directly.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.
Generally I'm open to that. I guess that depends a little on how we want to structure/explain the migration. I guess the current state is fairly minimal with the amount of actual breakage. My proposal for the trait bounds migration would have been to provide
impl<'a, 'py, T> IntoPyObject<'py> for &'a T where T: ToPyObject {}
this blanket, since the vast majority of the APIs are generic ofToPyObject
. I would hope that that would keep breakage still low, but it's probably gonna be higher that now. So if you prefer we can definitely delay that to 0.24On a different note, there is still a bit if bound api cleanup left that I think we should finish before 0.23 and I think #4449 we can also put in 0.23
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.
Hmm, interesting. So I played around with this (and ideas like a blanket-impl of
ToPyObject
fromIntoPyObject
, i.e. the reverse direction). TBH, neither felt great. For example implementingIntoPyObject for &'a T where T: ToPyObject
will only help when users pass references for their custom types. Having the blanket might just be more confusion.Having looked at that more, I think that in 0.23 we should just go for it and migrate all trait bounds without a blanket and commit to the bigger breakage. While it's a big (ish) breakage, I think it's actually the easiest state for users to understand, and I think we can make the migration easier for users by adding the derive proposed in #4458. (They might then just be able to switch to the derive and delete code in a lot of cases).
That said, I think we need to cut a 0.22.3 release to resolve #4452 and ship #4396, so I am open to the idea of merging this PR as-is and cherry-picking it as a perf enhancement in 0.22.3. @ChayimFriedman2, if we did that, would you be willing to help work on the follow-up to move this off
IntoPy
and onto new traits?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.
Yes. Ping me when you need my help.
I'm actually trying to work now on a
pycall!()
draft, which will be both the most performant, most capable and most convenient way to call a Python method. Let's see where this'll bring us (it is still worth landing this PR because it benefits user we haven't migrated).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.
That's true, haven't thought of that. In that case I think I tend to agree, providing any blanket will probably make it worse.
Sure thing, I'll prepare the PR with the trait bounds change and afterwards look into the derive macro.