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

update extract_argument to use Bound APIs #3708

Merged
merged 12 commits into from
Feb 28, 2024

Conversation

davidhewitt
Copy link
Member

Builds upon #3681 to move our internal argument extraction mechanisms off the GIL Pool.

Only the last commit is new, and I don't think this is worth reviewing right until #3681 is in, after which I'll rebase and tidy this a bit further. Pushing now purely because I think this should hopefully demonstrate a tantalising performance speedup which we're so close to merging! 😂

Copy link

codspeed-hq bot commented Dec 28, 2023

CodSpeed Performance Report

Merging #3708 will improve performances by 13.02%

Comparing davidhewitt:extract-arg2 (101563c) with main (a15e4b1)

Summary

⚡ 2 improvements
✅ 65 untouched benchmarks

Benchmarks breakdown

Benchmark main davidhewitt:extract-arg2 Change
test_simple_args_kwargs_rs 35.3 µs 31.9 µs +10.64%
test_simple_kwargs_rs 35 µs 31 µs +13.02%

@davidhewitt
Copy link
Member Author

Hmm I'll investigate later why this hasn't yielded the speedup I've seen in #3606, there's likely some other part I need to merge first...

@davidhewitt
Copy link
Member Author

Ah, I think it's PyDict::new_bound which is missing, I'll open a PR for that once we've merged the feature proposed in #3707

@davidhewitt davidhewitt added the CI-skip-changelog Skip checking changelog entry label Dec 31, 2023
@davidhewitt davidhewitt force-pushed the extract-arg2 branch 3 times, most recently from 3555727 to 5dc78b7 Compare December 31, 2023 22:38
@davidhewitt davidhewitt force-pushed the extract-arg2 branch 3 times, most recently from 36189d4 to 1fe867b Compare February 3, 2024 07:09
@davidhewitt
Copy link
Member Author

The performance regressions with the ordered_X benchmarks are genuine and look like they're caused because part of the proc macros go through &PyCell<T> GIL Ref machinery, and the compatibility implementation of FromPyObject::extract_bound is hurting this.

Probably the right approach is to get this PR merged (well, needs #3784 reviewed and merged first) and also #3792. Then I think there will be enough pieces in place to deal with updating the proc macro internals in a follow-up.

@davidhewitt
Copy link
Member Author

This is getting closer to being reviewable, but I think first it was worth splitting out #3801 and #3802.

Copy link
Member Author

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whew, I think this is finally ready to review and I'll deal with the PyRef/PyCell changes separately.

src/instance.rs Outdated Show resolved Hide resolved
src/err/mod.rs Outdated Show resolved Hide resolved
Comment on lines +7 to +9
type Any<'py> = Bound<'py, PyAny>;
type Dict<'py> = Bound<'py, PyDict>;
type Tuple<'py> = Bound<'py, PyTuple>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clippy was warning about type complexity in the return value tuples without defining these aliases.

@@ -357,7 +357,7 @@ impl<'py> PyStringMethods<'py> for Bound<'py, PyString> {
impl<'a> Borrowed<'a, '_, PyString> {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[allow(clippy::wrong_self_convention)]
fn to_str(self) -> PyResult<&'a str> {
pub(crate) fn to_str(self) -> PyResult<&'a str> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used in keyword argument extraction from borrowed keyword names.

Comment on lines 520 to 528
impl<'py> Bound<'py, PyDict> {
/// Iterates over the contents of this dictionary without incrementing reference counts.
///
/// # Safety
/// It must be known that this dictionary will not be modified during iteration.
pub(crate) unsafe fn iter_borrowed<'a>(&'a self) -> BorrowedDictIter<'a, 'py> {
BorrowedDictIter::new(self)
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of adding this crate-private API was that I wanted to keep keyword argument processing using Borrowed for both the "fastcall" and default calling conventions. As far as I am aware, argument processing can safely process a kwargs dict in this way because it's the only referee of the kwargs dict.

@davidhewitt davidhewitt marked this pull request as ready for review February 17, 2024 01:00
Comment on lines +586 to +587
let result = #call;
result
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes to return result on a separate line were caused by the fact that #call creates a borrow on _args and _kwargs, which now have Drop implementations due to being Bound<PyTuple> and Option<Bound<PyDict>> (if *args, or **kwargs are accepted).

So to satisfy the borrow checker we had to split the expression like this.

I think given our decision in macro code is to trend away from a single giant expression anyway, and it'll help me in #3847 where I need to do this too, this seemed a reasonable adjustment to me.

Copy link
Contributor

@LilyFoote LilyFoote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spotted one nit.

src/err/mod.rs Outdated Show resolved Hide resolved
Co-authored-by: Lily Foote <code@lilyf.org>
if let Some(kwnames) = py.from_borrowed_ptr_or_opt::<PyTuple>(kwnames) {

// Safety: kwnames is known to be a pointer to a tuple, or null
let kwnames: &'py Option<Bound<'py, PyTuple>> = std::mem::transmute(&kwnames);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we want to create a Bound::ref_from_ptr_or_opt to avoid std::mem::transmute?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end I adjusted the iter_borrowed() implementations to allow calling them from a Borrowed instance, and then I was able to use Borrowed::from_ptr methods in this code here. I think it's fine to use Borrowed instead of ref_from_ptr here because we can control that pesky borrowed lifetime 'a without worrying about user code, unlike the macros.

src/impl_/extract_argument.rs Outdated Show resolved Hide resolved
src/impl_/extract_argument.rs Outdated Show resolved Hide resolved
@davidhewitt
Copy link
Member Author

Thanks for the reviews! I've removed the PyArg newtype following #3858, which has made this implementation a fair bit nicer on the macro side. I'll try to find time to tidy the rest up later.

Copy link
Contributor

@LilyFoote LilyFoote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Just a few nitpicks.

src/impl_/extract_argument.rs Outdated Show resolved Hide resolved
src/impl_/extract_argument.rs Outdated Show resolved Hide resolved
src/impl_/extract_argument.rs Outdated Show resolved Hide resolved
src/types/dict.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@Icxolu Icxolu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spotted a few Borrowed::from_ptr calls in proc macro generated code. It might be nice to replace them with BoundRef if possible, for a bit of extra safety. Otherwise LGTM!

pyo3-macros-backend/src/method.rs Outdated Show resolved Hide resolved
pyo3-macros-backend/src/params.rs Outdated Show resolved Hide resolved
pyo3-macros-backend/src/pymethod.rs Outdated Show resolved Hide resolved
@davidhewitt
Copy link
Member Author

Thanks both, I'll aim to tidy this up tomorrow 👍

@davidhewitt
Copy link
Member Author

Thanks both, think we're finally there on this one!

@davidhewitt davidhewitt added this pull request to the merge queue Feb 28, 2024
Merged via the queue into PyO3:main with commit 8a12970 Feb 28, 2024
42 checks passed
@davidhewitt davidhewitt deleted the extract-arg2 branch February 28, 2024 20:29
@MarshalX
Copy link

Hmm I'll investigate later why this hasn't yielded the speedup I've seen in #3606, there's likely some other part I need to merge first...

What is the latest result btw?

@davidhewitt
Copy link
Member Author

#3708 (comment), but I think it's quite likely that parts of the speedup already got merged now, given how many times this got split up.

I will probably analyze again some time and see if there's any further tricks to apply here, but for now I think this is good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI-skip-changelog Skip checking changelog entry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants