-
Notifications
You must be signed in to change notification settings - Fork 136
Optimize Ref<_, T> methods when T: Sized (#2752) #2758
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
Summary of ChangesHello @bmberger, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces targeted optimizations for Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
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.
Code Review
This pull request introduces an optimization for Sized types in several methods of Ref<_, T>. The logic seems sound, and the addition of benchmarks is a great way to validate the performance impact. My main feedback is regarding significant code duplication across four methods, which could be refactored to improve maintainability. I've provided a suggestion to use a macro to deduplicate this logic.
3960f56 to
f68da63
Compare
Benchmarks are surprisingly showing minimal impact... With optimization: test r#ref::tests::bench_deref_mut_sized ... bench: 0.72 ns/iter (+/- 0.09) test r#ref::tests::bench_deref_sized ... bench: 0.70 ns/iter (+/- 0.09) test r#ref::tests::bench_from_bytes_sized ... bench: 5.27 ns/iter (+/- 0.69) test r#ref::tests::bench_into_mut_sized ... bench: 0.70 ns/iter (+/- 0.07) test r#ref::tests::bench_into_ref_sized ... bench: 0.94 ns/iter (+/- 0.08) Without optimization: test r#ref::tests::bench_deref_mut_sized ... bench: 0.72 ns/iter (+/- 0.23) test r#ref::tests::bench_deref_sized ... bench: 0.73 ns/iter (+/- 0.09) test r#ref::tests::bench_from_bytes_sized ... bench: 5.26 ns/iter (+/- 0.69) test r#ref::tests::bench_into_mut_sized ... bench: 0.70 ns/iter (+/- 0.08) test r#ref::tests::bench_into_ref_sized ... bench: 0.97 ns/iter (+/- 0.08) Closes google#2752 Co-authored-by: Joshua Liebow-Feeser <[email protected]>
f68da63 to
08ba7b2
Compare
| __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS, | ||
| feature(layout_for_ptr, coverage_attribute) | ||
| )] | ||
| #![cfg_attr(test, feature(test))] |
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.
| #![cfg_attr(test, feature(test))] | |
| #![cfg_attr(all(test, __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS), feature(test))] |
This ensures that we only compile benchmarks on the nightly compiler, which is required since feature(test) is an unstable (ie, nightly-only) Rust feature.
| #[cfg(any(feature = "derive", test))] | ||
| extern crate self as zerocopy; | ||
|
|
||
| #[cfg(test)] |
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.
| #[cfg(test)] | |
| #[cfg(all(test, __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS))] |
| ( | ||
| BecauseMutationCompatible, | ||
| ( | ||
| BecauseRead, | ||
| (BecauseExclusive, BecauseExclusive), | ||
| ), | ||
| ), |
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.
You should be able to replace some of these with underscores (at the most extreme, (_, (_, (_, _)))) and inference will still work – that'll make this more concise and format more nicely.
| ( | ||
| BecauseMutationCompatible, | ||
| ( | ||
| BecauseRead, | ||
| (BecauseExclusive, BecauseExclusive), | ||
| ), | ||
| ), |
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.
Same here re: underscores
| ( | ||
| BecauseMutationCompatible, | ||
| ( | ||
| BecauseRead, | ||
| (BecauseExclusive, BecauseExclusive), | ||
| ), | ||
| ), |
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.
Same here re: underscores
| // SAFETY: The safety invariants of `Ptr::new` (see definition) are | ||
| // satisfied: | ||
| // 0. If `ptr`'s referent is not zero sized, then `ptr` has valid | ||
| // provenance for its referent, because it derived from `self` | ||
| // using a series of provenance-preserving operations, and | ||
| // because `self` has valid provenance for its referent. By the | ||
| // same argument, `ptr`'s referent is entirely contained within | ||
| // the same allocated object as `self`'s referent. | ||
| // 1. If `ptr`'s referent is not zero sized, then the allocation of | ||
| // `ptr` is guaranteed to live for at least `'a`, because `ptr` | ||
| // is entirely contained in `self`, which lives for at least `'a` | ||
| // by invariant on `Ptr`. |
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.
Same as above
| let ptr = T::raw_from_ptr_len( | ||
| ptr.as_non_null().cast(), | ||
| <T::PointerMetadata as crate::PointerMetadata>::from_elem_count(0), | ||
| ); |
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.
| let ptr = T::raw_from_ptr_len( | |
| ptr.as_non_null().cast(), | |
| <T::PointerMetadata as crate::PointerMetadata>::from_elem_count(0), | |
| ); | |
| let p = T::raw_from_ptr_len( | |
| ptr.as_non_null().cast(), | |
| <T::PointerMetadata as crate::PointerMetadata>::from_elem_count(0), | |
| ); |
| // SAFETY: The safety invariants of `Ptr::new` (see definition) are | ||
| // satisfied: | ||
| // 0. If `ptr`'s referent is not zero sized, then `ptr` has valid | ||
| // provenance for its referent, because it derived from `self` | ||
| // using a series of provenance-preserving operations, and | ||
| // because `self` has valid provenance for its referent. By the | ||
| // same argument, `ptr`'s referent is entirely contained within | ||
| // the same allocated object as `self`'s referent. | ||
| // 1. If `ptr`'s referent is not zero sized, then the allocation of | ||
| // `ptr` is guaranteed to live for at least `'a`, because `ptr` | ||
| // is entirely contained in `self`, which lives for at least `'a` | ||
| // by invariant on `Ptr`. |
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.
Same as above
| #[bench] | ||
| fn bench_from_bytes_sized(b: &mut Bencher) { | ||
| let buf = Align::<[u8; 8], AU64>::default(); | ||
| // `buf.t` should be aligned to 8, so this should always succeed. | ||
| let bytes = &buf.t[..]; | ||
| b.iter(|| test::black_box(Ref::<_, AU64>::from_bytes(test::black_box(bytes)).unwrap())); | ||
| } | ||
|
|
||
| #[bench] | ||
| fn bench_into_ref_sized(b: &mut Bencher) { | ||
| let buf = Align::<[u8; 8], AU64>::default(); | ||
| let bytes = &buf.t[..]; | ||
| let r = Ref::<_, AU64>::from_bytes(bytes).unwrap(); | ||
| b.iter(|| { | ||
| test::black_box(Ref::into_ref(test::black_box(r))) | ||
| }); | ||
| } | ||
|
|
||
| #[bench] | ||
| fn bench_into_mut_sized(b: &mut Bencher) { | ||
| let mut buf = Align::<[u8; 8], AU64>::default(); | ||
| let _ = Ref::<_, AU64>::from_bytes(&mut buf.t[..]).unwrap(); | ||
| b.iter(move || { | ||
| // SAFETY: The preceding `from_bytes` succeeded, and so we know that | ||
| // `&mut buf.t[.] | ||
| let r = unsafe { Ref::<&mut [u8], AU64>::new_unchecked(&mut buf.t[..]) }; | ||
| test::black_box(Ref::into_mut(test::black_box(r))); | ||
| }); | ||
| } | ||
|
|
||
| #[bench] | ||
| fn bench_deref_sized(b: &mut Bencher) { | ||
| let buf = Align::<[u8; 8], AU64>::default(); | ||
| let bytes = &buf.t[..]; | ||
| let r = Ref::<_, AU64>::from_bytes(bytes).unwrap(); | ||
| b.iter(|| { | ||
| let temp = test::black_box(r); | ||
| test::black_box(temp.deref()); | ||
| }); | ||
| } | ||
|
|
||
| #[bench] | ||
| fn bench_deref_mut_sized(b: &mut Bencher) { | ||
| let mut buf = Align::<[u8; 8], AU64>::default(); | ||
| let _ = Ref::<_, AU64>::from_bytes(&mut buf.t[..]).unwrap(); | ||
| b.iter(|| { | ||
| // SAFETY: The preceding `from_bytes` succeeded, and so we know that | ||
| // `&mut buf.t[.] | ||
| let r = unsafe { Ref::<&mut [u8], AU64>::new_unchecked(&mut buf.t[..]) }; | ||
| let mut temp = test::black_box(r); | ||
| test::black_box(temp.deref_mut()); | ||
| }); | ||
| } |
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.
Let's move these into a new #[cfg(all(test, __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS))] mod benches below mod tests?
| let ptr: Ptr<'_, T, (Shared, Unaligned, Initialized)> = unsafe { | ||
| ptr.cast_unsized(|ptr| { | ||
| let ptr = T::raw_from_ptr_len( | ||
| ptr.as_non_null().cast(), | ||
| <T::PointerMetadata as crate::PointerMetadata>::from_elem_count(0), | ||
| ); | ||
| // SAFETY: The safety invariants of `Ptr::new` (see definition) are | ||
| // satisfied: | ||
| // 0. If `ptr`'s referent is not zero sized, then `ptr` has valid | ||
| // provenance for its referent, because it derived from `self` | ||
| // using a series of provenance-preserving operations, and | ||
| // because `self` has valid provenance for its referent. By the | ||
| // same argument, `ptr`'s referent is entirely contained within | ||
| // the same allocated object as `self`'s referent. | ||
| // 1. If `ptr`'s referent is not zero sized, then the allocation of | ||
| // `ptr` is guaranteed to live for at least `'a`, because `ptr` | ||
| // is entirely contained in `self`, which lives for at least `'a` | ||
| // by invariant on `Ptr`. | ||
| unsafe { PtrInner::new(ptr) } | ||
| }) | ||
| }; |
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.
You should be able to factor this entire thing out into a private, unsafe bare function below with its own separate safety preconditions. If you make it generic over the aliasing type parameter, you should be able to have a single function that's used by all four methods.
Benchmarks are surprisingly showing minimal impact...
With optimization:
test r#ref::tests::bench_deref_mut_sized ... bench: 0.23 ns/iter (+/- 0.03)
test r#ref::tests::bench_deref_sized ... bench: 0.44 ns/iter (+/- 0.04)
test r#ref::tests::bench_from_bytes_sized ... bench: 4.64 ns/iter (+/- 0.03)
test r#ref::tests::bench_into_mut_sized ... bench: 0.23 ns/iter (+/- 0.03)
test r#ref::tests::bench_into_ref_sized ... bench: 0.44 ns/iter (+/- 0.00)
Without optimization:
test r#ref::tests::bench_deref_mut_sized ... bench: 0.22 ns/iter (+/- 0.02)
test r#ref::tests::bench_deref_sized ... bench: 0.44 ns/iter (+/- 0.03)
test r#ref::tests::bench_from_bytes_sized ... bench: 4.66 ns/iter (+/- 0.31)
test r#ref::tests::bench_into_mut_sized ... bench: 0.23 ns/iter (+/- 0.03)
test r#ref::tests::bench_into_ref_sized ... bench: 0.44 ns/iter (+/- 0.01)
Closes #2752