From cdfa85ba44644f7ea5b8cb96f49d604d6254f69b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 22 Feb 2022 14:59:01 +0100 Subject: [PATCH 1/3] Benchmark objc_retainAutoreleasedReturnValue using iai --- objc2/Cargo.toml | 7 ++ objc2/benches/autorelease.rs | 175 +++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 objc2/benches/autorelease.rs diff --git a/objc2/Cargo.toml b/objc2/Cargo.toml index 896d7091a..640deabb6 100644 --- a/objc2/Cargo.toml +++ b/objc2/Cargo.toml @@ -48,6 +48,13 @@ objc2-encode = { path = "../objc2-encode", version = "=2.0.0-beta.2" } [build-dependencies] cc = { version = "1", optional = true } +[dev-dependencies] +iai = { version = "0.1", git = "https://github.com/madsmtm/iai", branch = "callgrind" } + +[[bench]] +name = "autorelease" +harness = false + [package.metadata.docs.rs] default-target = "x86_64-apple-darwin" diff --git a/objc2/benches/autorelease.rs b/objc2/benches/autorelease.rs new file mode 100644 index 000000000..ec6b45a7d --- /dev/null +++ b/objc2/benches/autorelease.rs @@ -0,0 +1,175 @@ +use core::ffi::c_void; +use std::mem::ManuallyDrop; +use std::ptr::NonNull; + +use objc2::ffi; +use objc2::rc::{autoreleasepool, Id, Shared}; +use objc2::runtime::{Class, Object, Sel}; +use objc2::{class, msg_send, sel}; + +#[cfg(apple)] +#[link(name = "Foundation", kind = "framework")] +extern "C" {} + +#[cfg(gnustep)] +#[link(name = "gnustep-base", kind = "dylib")] +extern "C" {} + +const BYTES: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + +fn empty() { + #[cfg(gnustep)] + unsafe { + objc2::__gnustep_hack::get_class_to_force_linkage() + }; +} + +fn pool_cleanup() { + autoreleasepool(|_| {}) +} + +fn class() -> &'static Class { + class!(NSObject) +} + +fn sel() -> Sel { + sel!(alloc) +} + +fn send_message() -> &'static Class { + unsafe { msg_send![class!(NSObject), class] } +} + +fn alloc_nsobject() -> *mut Object { + unsafe { msg_send![class!(NSObject), alloc] } +} + +fn new_nsobject() -> Id { + let obj = alloc_nsobject(); + let obj: *mut Object = unsafe { msg_send![obj, init] }; + unsafe { Id::new(NonNull::new_unchecked(obj)) } +} + +fn new_nsdata() -> Id { + let bytes_ptr = BYTES.as_ptr() as *const c_void; + let obj: *mut Object = unsafe { msg_send![class!(NSData), alloc] }; + let obj: *mut Object = unsafe { + msg_send![ + obj, + initWithBytes: bytes_ptr, + length: BYTES.len(), + ] + }; + unsafe { Id::new(NonNull::new_unchecked(obj)) } +} + +fn new_leaked_nsdata() -> *mut Object { + ManuallyDrop::new(new_nsdata()).as_ptr() +} + +fn autoreleased_nsdata() -> *mut Object { + // let bytes_ptr = BYTES.as_ptr() as *const c_void; + // unsafe { + // msg_send![ + // class!(NSData), + // dataWithBytes: bytes_ptr, + // length: BYTES.len(), + // ] + // } + unsafe { msg_send![new_leaked_nsdata(), autorelease] } +} + +fn new_nsstring() -> Id { + let obj: *mut Object = unsafe { msg_send![class!(NSString), alloc] }; + let obj: *mut Object = unsafe { msg_send![obj, init] }; + unsafe { Id::new(NonNull::new_unchecked(obj)) } +} + +fn new_leaked_nsstring() -> *mut Object { + ManuallyDrop::new(new_nsstring()).as_ptr() +} + +fn autoreleased_nsstring() -> *mut Object { + // unsafe { msg_send![class!(NSString), string] } + unsafe { msg_send![new_leaked_nsstring(), autorelease] } +} + +fn retain_autoreleased(obj: *mut Object) -> Id { + let obj = unsafe { ffi::objc_retainAutoreleasedReturnValue(obj.cast()) }; + unsafe { Id::new(NonNull::new_unchecked(obj).cast()) } +} + +fn autoreleased_nsdata_pool_cleanup() -> *mut Object { + autoreleasepool(|_| autoreleased_nsdata()) +} + +fn autoreleased_nsdata_fast_caller_cleanup() -> Id { + retain_autoreleased(autoreleased_nsdata()) +} + +fn autoreleased_nsdata_fast_caller_cleanup_pool_cleanup() -> Id { + autoreleasepool(|_| retain_autoreleased(autoreleased_nsdata())) +} + +fn autoreleased_nsstring_pool_cleanup() -> *mut Object { + autoreleasepool(|_| autoreleased_nsstring()) +} + +fn autoreleased_nsstring_fast_caller_cleanup() -> Id { + retain_autoreleased(autoreleased_nsstring()) +} + +fn autoreleased_nsstring_fast_caller_cleanup_pool_cleanup() -> Id { + autoreleasepool(|_| retain_autoreleased(autoreleased_nsstring())) +} + +macro_rules! main_with_warmup { + ($($f:ident,)+) => { + mod warmup_fns { + $( + #[inline(never)] + pub fn $f() { + let _ = iai::black_box(super::$f()); + } + )+ + } + + fn warmup() { + $( + warmup_fns::$f(); + )+ + } + + iai::main! { + warmup, + $( + $f, + )+ + } + }; +} + +main_with_warmup! { + // Baseline + empty, + pool_cleanup, + class, + sel, + send_message, + alloc_nsobject, + new_nsobject, + // NSData + new_nsdata, + new_leaked_nsdata, + autoreleased_nsdata, + autoreleased_nsdata_pool_cleanup, + autoreleased_nsdata_fast_caller_cleanup, + autoreleased_nsdata_fast_caller_cleanup_pool_cleanup, + // NSString + new_nsstring, + new_leaked_nsstring, + autoreleased_nsstring, + autoreleased_nsstring_pool_cleanup, + autoreleased_nsstring_fast_caller_cleanup, + autoreleased_nsstring_fast_caller_cleanup_pool_cleanup, +} From 50b47ca7ccd72846951e93ca3475de2ceb61c302 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 22 Feb 2022 14:24:36 +0100 Subject: [PATCH 2/3] Better inlining --- objc2/src/rc/autorelease.rs | 8 ++++---- objc2/src/rc/id.rs | 4 ++-- objc2/src/rc/id_traits.rs | 1 + objc2/src/rc/weak_id.rs | 7 +++++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/objc2/src/rc/autorelease.rs b/objc2/src/rc/autorelease.rs index 0e8153a3b..faccda25d 100644 --- a/objc2/src/rc/autorelease.rs +++ b/objc2/src/rc/autorelease.rs @@ -45,6 +45,7 @@ impl AutoreleasePool { /// Additionally, the pools must be dropped in the same order they were /// created. #[doc(alias = "objc_autoreleasePoolPush")] + #[inline] unsafe fn new() -> Self { // TODO: Make this function pub when we're more certain of the API let context = unsafe { ffi::objc_autoreleasePoolPush() }; @@ -57,10 +58,7 @@ impl AutoreleasePool { } /// This will be removed in a future version. - #[cfg_attr( - not(all(debug_assertions, not(feature = "unstable_autoreleasesafe"))), - inline - )] + #[inline] #[doc(hidden)] pub fn __verify_is_inner(&self) { #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] @@ -139,6 +137,7 @@ impl Drop for AutoreleasePool { /// [clang documentation]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#autoreleasepool /// [revision `371`]: https://github.com/apple-oss-distributions/objc4/blob/objc4-371/runtime/objc-exception.m#L479-L482 #[doc(alias = "objc_autoreleasePoolPop")] + #[inline] fn drop(&mut self) { unsafe { ffi::objc_autoreleasePoolPop(self.context) } #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] @@ -292,6 +291,7 @@ impl !AutoreleaseSafe for AutoreleasePool {} /// # panic!("Does not panic in release mode, so for testing we make it!"); /// ``` #[doc(alias = "@autoreleasepool")] +#[inline(always)] pub fn autoreleasepool(f: F) -> T where for<'p> F: FnOnce(&'p AutoreleasePool) -> T + AutoreleaseSafe, diff --git a/objc2/src/rc/id.rs b/objc2/src/rc/id.rs index 0fc88c6f8..01c3c215c 100644 --- a/objc2/src/rc/id.rs +++ b/objc2/src/rc/id.rs @@ -237,7 +237,7 @@ impl Id { // let y: &T = &*retained; // ``` #[doc(alias = "objc_retain")] - #[cfg_attr(not(debug_assertions), inline)] + #[inline] pub unsafe fn retain(ptr: NonNull) -> Id { let ptr = ptr.as_ptr() as *mut ffi::objc_object; // SAFETY: The caller upholds that the pointer is valid @@ -266,7 +266,7 @@ impl Id { NonNull::new(ptr).map(|ptr| unsafe { Id::retain(ptr) }) } - #[cfg_attr(not(debug_assertions), inline)] + #[inline] fn autorelease_inner(self) -> *mut T { // Note that this (and the actual `autorelease`) is not an associated // function. This breaks the guideline that smart pointers shouldn't diff --git a/objc2/src/rc/id_traits.rs b/objc2/src/rc/id_traits.rs index 1e575579e..3184e0e1e 100644 --- a/objc2/src/rc/id_traits.rs +++ b/objc2/src/rc/id_traits.rs @@ -65,6 +65,7 @@ pub trait DefaultId { } impl Default for Id { + #[inline] fn default() -> Self { T::default_id() } diff --git a/objc2/src/rc/weak_id.rs b/objc2/src/rc/weak_id.rs index e739d0507..2fe2557f9 100644 --- a/objc2/src/rc/weak_id.rs +++ b/objc2/src/rc/weak_id.rs @@ -4,7 +4,6 @@ use core::convert::TryFrom; use core::fmt; use core::marker::PhantomData; use core::ptr; -use core::ptr::NonNull; use std::panic::{RefUnwindSafe, UnwindSafe}; use super::{Id, Shared}; @@ -35,6 +34,7 @@ pub struct WeakId { impl WeakId { /// Construct a new [`WeakId`] referencing the given shared [`Id`]. #[doc(alias = "objc_initWeak")] + #[inline] pub fn new(obj: &Id) -> Self { // Note that taking `&Id` would not be safe since that would // allow loading an `Id` later on. @@ -68,7 +68,7 @@ impl WeakId { pub fn load(&self) -> Option> { let ptr: *mut *mut ffi::objc_object = self.inner.get() as _; let obj = unsafe { ffi::objc_loadWeakRetained(ptr) } as *mut T; - NonNull::new(obj).map(|obj| unsafe { Id::new(obj) }) + unsafe { Id::new_null(obj) } } // TODO: Add `autorelease(&self) -> Option<&T>` using `objc_loadWeak`? @@ -77,6 +77,7 @@ impl WeakId { impl Drop for WeakId { /// Drops the `WeakId` pointer. #[doc(alias = "objc_destroyWeak")] + #[inline] fn drop(&mut self) { unsafe { ffi::objc_destroyWeak(self.inner.get() as _) } } @@ -101,6 +102,7 @@ impl Default for WeakId { /// Constructs a new `WeakId` that doesn't reference any object. /// /// Calling [`Self::load`] on the return value always gives [`None`]. + #[inline] fn default() -> Self { // SAFETY: The pointer is null unsafe { Self::new_inner(ptr::null_mut()) } @@ -130,6 +132,7 @@ impl RefUnwindSafe for WeakId {} impl UnwindSafe for WeakId {} impl From> for WeakId { + #[inline] fn from(obj: Id) -> Self { WeakId::new(&obj) } From 22dd211bf835dd2d53c259fc0e884f01311e0d3e Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 22 Feb 2022 14:51:48 +0100 Subject: [PATCH 3/3] Run benchmarks in CI --- .github/workflows/ci.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a12c8027..d134e7e11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -186,9 +186,9 @@ jobs: # This changes a variable, so is only set when a custom SDK is used run: echo "SDKROOT=$HOME/extern/sdk" >> $GITHUB_ENV - - name: Install Clang + - name: Install Clang & Valgrind if: contains(matrix.os, 'ubuntu') - run: sudo apt-get -y install clang + run: sudo apt-get -y install clang valgrind - name: Install cross compilation tools if: matrix.target == 'i686-unknown-linux-gnu' @@ -327,6 +327,15 @@ jobs: command: test args: --features ${{ env.FEATURES }} ${{ env.TESTARGS }} --release + - name: Run benchmarks + # Difficult to install Valgrind on macOS + # See https://github.com/LouisBrunner/valgrind-macos + if: contains(matrix.os, 'ubuntu') + uses: actions-rs/cargo@v1 + with: + command: bench + args: --bench autorelease ${{ env.TESTARGS }} + - name: Test with unstable features if: ${{ !matrix.dinghy && matrix.rust.toolchain == 'nightly' }} uses: actions-rs/cargo@v1