Skip to content

Commit 2d1d2b2

Browse files
bmbergerjoshlf
andcommitted
Optimize Ref<_, T> methods when T: Sized (#2752)
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 Co-authored-by: Joshua Liebow-Feeser <[email protected]>
1 parent e8eb595 commit 2d1d2b2

File tree

2 files changed

+168
-2
lines changed

2 files changed

+168
-2
lines changed

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,17 @@
334334
__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS,
335335
feature(layout_for_ptr, coverage_attribute)
336336
)]
337+
#![cfg_attr(test, feature(test))]
337338

338339
// This is a hack to allow zerocopy-derive derives to work in this crate. They
339340
// assume that zerocopy is linked as an extern crate, so they access items from
340341
// it as `zerocopy::Xxx`. This makes that still work.
341342
#[cfg(any(feature = "derive", test))]
342343
extern crate self as zerocopy;
343344

345+
#[cfg(test)]
346+
extern crate test;
347+
344348
#[doc(hidden)]
345349
#[macro_use]
346350
pub mod util;

src/ref.rs

Lines changed: 164 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,36 @@ where
616616
// SAFETY: We don't call any methods on `b` other than those provided by
617617
// `IntoByteSlice`.
618618
let b = unsafe { r.into_byte_slice() };
619+
let b = b.into_byte_slice();
620+
621+
if let crate::layout::SizeInfo::Sized { .. } = T::LAYOUT.size_info {
622+
let ptr = Ptr::from_ref(b);
623+
// SAFETY: `T::raw_from_ptr_len` promises to preserve address and provenance in the referent
624+
let ptr: Ptr<'_, T, _> = unsafe {
625+
ptr.transmute_unchecked(|ptr| {
626+
let ptr = T::raw_from_ptr_len(
627+
ptr.as_non_null().cast(),
628+
<T::PointerMetadata as crate::PointerMetadata>::from_elem_count(0),
629+
);
630+
// SAFETY: The safety invariants of `Ptr::new` (see definition) are
631+
// satisfied:
632+
// 0. If `ptr`'s referent is not zero sized, then `ptr` has valid
633+
// provenance for its referent, because it derived from `self`
634+
// using a series of provenance-preserving operations, and
635+
// because `self` has valid provenance for its referent. By the
636+
// same argument, `ptr`'s referent is entirely contained within
637+
// the same allocated object as `self`'s referent.
638+
// 1. If `ptr`'s referent is not zero sized, then the allocation of
639+
// `ptr` is guaranteed to live for at least `'a`, because `ptr`
640+
// is entirely contained in `self`, which lives for at least `'a`
641+
// by invariant on `Ptr`.
642+
unsafe { crate::pointer::PtrInner::new(ptr) }
643+
})
644+
};
645+
// SAFETY: By invariant on `r`, we know that size and alignment are valid.
646+
let ptr = unsafe { ptr.assume_alignment() };
647+
return ptr.as_ref();
648+
}
619649

620650
// PANICS: By post-condition on `into_byte_slice`, `b`'s size and
621651
// alignment are valid for `T`. By post-condition, `b.into_byte_slice()`
@@ -650,6 +680,36 @@ where
650680
// SAFETY: We don't call any methods on `b` other than those provided by
651681
// `IntoByteSliceMut`.
652682
let b = unsafe { r.into_byte_slice_mut() };
683+
let b = b.into_byte_slice_mut();
684+
685+
if let crate::layout::SizeInfo::Sized { .. } = T::LAYOUT.size_info {
686+
let ptr = Ptr::from_mut(b);
687+
// SAFETY: `T::raw_from_ptr_len` promises to preserve address and provenance in the referent
688+
let ptr: Ptr<'_, T, _> = unsafe {
689+
ptr.transmute_unchecked(|ptr| {
690+
let ptr = T::raw_from_ptr_len(
691+
ptr.as_non_null().cast(),
692+
<T::PointerMetadata as crate::PointerMetadata>::from_elem_count(0),
693+
);
694+
// SAFETY: The safety invariants of `Ptr::new` (see definition) are
695+
// satisfied:
696+
// 0. If `ptr`'s referent is not zero sized, then `ptr` has valid
697+
// provenance for its referent, because it derived from `self`
698+
// using a series of provenance-preserving operations, and
699+
// because `self` has valid provenance for its referent. By the
700+
// same argument, `ptr`'s referent is entirely contained within
701+
// the same allocated object as `self`'s referent.
702+
// 1. If `ptr`'s referent is not zero sized, then the allocation of
703+
// `ptr` is guaranteed to live for at least `'a`, because `ptr`
704+
// is entirely contained in `self`, which lives for at least `'a`
705+
// by invariant on `Ptr`.
706+
unsafe { crate::pointer::PtrInner::new(ptr) }
707+
})
708+
};
709+
// SAFETY: By invariant on `r`, we know that size and alignment are valid.
710+
let ptr = unsafe { ptr.assume_alignment() };
711+
return ptr.as_mut();
712+
}
653713

654714
// PANICS: By post-condition on `into_byte_slice_mut`, `b`'s size and
655715
// alignment are valid for `T`. By post-condition,
@@ -763,11 +823,41 @@ where
763823
// SAFETY: We don't call any methods on `b` other than those provided by
764824
// `ByteSlice`.
765825
let b = unsafe { self.as_byte_slice() };
826+
let b = b.deref();
827+
828+
if let crate::layout::SizeInfo::Sized { .. } = T::LAYOUT.size_info {
829+
let ptr = Ptr::from_ref(b);
830+
// SAFETY: `T::raw_from_ptr_len` promises to preserve address and provenance in the referent
831+
let ptr: Ptr<'_, T, _> = unsafe {
832+
ptr.transmute_unchecked(|ptr| {
833+
let ptr = T::raw_from_ptr_len(
834+
ptr.as_non_null().cast(),
835+
<T::PointerMetadata as crate::PointerMetadata>::from_elem_count(0),
836+
);
837+
// SAFETY: The safety invariants of `Ptr::new` (see definition) are
838+
// satisfied:
839+
// 0. If `ptr`'s referent is not zero sized, then `ptr` has valid
840+
// provenance for its referent, because it derived from `self`
841+
// using a series of provenance-preserving operations, and
842+
// because `self` has valid provenance for its referent. By the
843+
// same argument, `ptr`'s referent is entirely contained within
844+
// the same allocated object as `self`'s referent.
845+
// 1. If `ptr`'s referent is not zero sized, then the allocation of
846+
// `ptr` is guaranteed to live for at least `'a`, because `ptr`
847+
// is entirely contained in `self`, which lives for at least `'a`
848+
// by invariant on `Ptr`.
849+
unsafe { crate::pointer::PtrInner::new(ptr) }
850+
})
851+
};
852+
// SAFETY: By invariant on `self`, we know that size and alignment are valid.
853+
let ptr = unsafe { ptr.assume_alignment() };
854+
return ptr.as_ref();
855+
}
766856

767857
// PANICS: By postcondition on `as_byte_slice`, `b`'s size and alignment
768858
// are valid for `T`, and by invariant on `ByteSlice`, these are
769859
// preserved through `.deref()`, so this `unwrap` will not panic.
770-
let ptr = Ptr::from_ref(b.deref())
860+
let ptr = Ptr::from_ref(b)
771861
.try_cast_into_no_leftover::<T, BecauseImmutable>(None)
772862
.expect("zerocopy internal error: Deref::deref should be infallible");
773863
let ptr = ptr.recall_validity();
@@ -791,12 +881,42 @@ where
791881
// SAFETY: We don't call any methods on `b` other than those provided by
792882
// `ByteSliceMut`.
793883
let b = unsafe { self.as_byte_slice_mut() };
884+
let b = b.deref_mut();
885+
886+
if let crate::layout::SizeInfo::Sized { .. } = T::LAYOUT.size_info {
887+
let ptr = Ptr::from_mut(b);
888+
// SAFETY: `T::raw_from_ptr_len` promises to preserve address and provenance in the referent
889+
let ptr: Ptr<'_, T, _> = unsafe {
890+
ptr.transmute_unchecked(|ptr| {
891+
let ptr = T::raw_from_ptr_len(
892+
ptr.as_non_null().cast(),
893+
<T::PointerMetadata as crate::PointerMetadata>::from_elem_count(0),
894+
);
895+
// SAFETY: The safety invariants of `Ptr::new` (see definition) are
896+
// satisfied:
897+
// 0. If `ptr`'s referent is not zero sized, then `ptr` has valid
898+
// provenance for its referent, because it derived from `self`
899+
// using a series of provenance-preserving operations, and
900+
// because `self` has valid provenance for its referent. By the
901+
// same argument, `ptr`'s referent is entirely contained within
902+
// the same allocated object as `self`'s referent.
903+
// 1. If `ptr`'s referent is not zero sized, then the allocation of
904+
// `ptr` is guaranteed to live for at least `'a`, because `ptr`
905+
// is entirely contained in `self`, which lives for at least `'a`
906+
// by invariant on `Ptr`.
907+
unsafe { crate::pointer::PtrInner::new(ptr) }
908+
})
909+
};
910+
// SAFETY: By invariant on `self`, we know that size and alignment are valid.
911+
let ptr = unsafe { ptr.assume_alignment() };
912+
return ptr.as_mut();
913+
}
794914

795915
// PANICS: By postcondition on `as_byte_slice_mut`, `b`'s size and
796916
// alignment are valid for `T`, and by invariant on `ByteSlice`, these
797917
// are preserved through `.deref_mut()`, so this `unwrap` will not
798918
// panic.
799-
let ptr = Ptr::from_mut(b.deref_mut())
919+
let ptr = Ptr::from_mut(b)
800920
.try_cast_into_no_leftover::<T, BecauseExclusive>(None)
801921
.expect("zerocopy internal error: DerefMut::deref_mut should be infallible");
802922
let ptr = ptr.recall_validity::<_, (_, (_, (BecauseExclusive, BecauseExclusive)))>();
@@ -879,6 +999,48 @@ mod tests {
879999

8801000
use super::*;
8811001
use crate::util::testutil::*;
1002+
use test::{self, Bencher};
1003+
1004+
#[bench]
1005+
fn bench_from_bytes_sized(b: &mut Bencher) {
1006+
let buf = Align::<[u8; 8], AU64>::default();
1007+
// `buf.t` should be aligned to 8, so this should always succeed.
1008+
b.iter(|| test::black_box(Ref::<_, AU64>::from_bytes(&buf.t[..]).unwrap()));
1009+
}
1010+
1011+
#[bench]
1012+
fn bench_into_ref_sized(b: &mut Bencher) {
1013+
let buf = Align::<[u8; 8], AU64>::default();
1014+
b.iter(|| {
1015+
let r = Ref::<_, AU64>::from_bytes(&buf.t[..]).unwrap();
1016+
test::black_box(Ref::into_ref(r))
1017+
});
1018+
}
1019+
1020+
#[bench]
1021+
fn bench_into_mut_sized(b: &mut Bencher) {
1022+
let mut buf = Align::<[u8; 8], AU64>::default();
1023+
b.iter(|| {
1024+
let r = Ref::<_, AU64>::from_bytes(&mut buf.t[..]).unwrap();
1025+
test::black_box(Ref::into_mut(r));
1026+
});
1027+
}
1028+
1029+
#[bench]
1030+
fn bench_deref_sized(b: &mut Bencher) {
1031+
let buf = Align::<[u8; 8], AU64>::default();
1032+
let r = Ref::<_, AU64>::from_bytes(&buf.t[..]).unwrap();
1033+
b.iter(|| test::black_box(r.deref()));
1034+
}
1035+
1036+
#[bench]
1037+
fn bench_deref_mut_sized(b: &mut Bencher) {
1038+
let mut buf = Align::<[u8; 8], AU64>::default();
1039+
b.iter(|| {
1040+
let mut r = Ref::<_, AU64>::from_bytes(&mut buf.t[..]).unwrap();
1041+
test::black_box(r.deref_mut());
1042+
});
1043+
}
8821044

8831045
#[test]
8841046
fn test_mut_slice_into_ref() {

0 commit comments

Comments
 (0)