Skip to content

Commit 3fc99b5

Browse files
authored
Merge pull request #122 from madsmtm/criterion-benchmarking
Start benchmarking
2 parents f23c3fc + 22dd211 commit 3fc99b5

File tree

7 files changed

+205
-10
lines changed

7 files changed

+205
-10
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,9 @@ jobs:
186186
# This changes a variable, so is only set when a custom SDK is used
187187
run: echo "SDKROOT=$HOME/extern/sdk" >> $GITHUB_ENV
188188

189-
- name: Install Clang
189+
- name: Install Clang & Valgrind
190190
if: contains(matrix.os, 'ubuntu')
191-
run: sudo apt-get -y install clang
191+
run: sudo apt-get -y install clang valgrind
192192

193193
- name: Install cross compilation tools
194194
if: matrix.target == 'i686-unknown-linux-gnu'
@@ -327,6 +327,15 @@ jobs:
327327
command: test
328328
args: --features ${{ env.FEATURES }} ${{ env.TESTARGS }} --release
329329

330+
- name: Run benchmarks
331+
# Difficult to install Valgrind on macOS
332+
# See https://github.com/LouisBrunner/valgrind-macos
333+
if: contains(matrix.os, 'ubuntu')
334+
uses: actions-rs/cargo@v1
335+
with:
336+
command: bench
337+
args: --bench autorelease ${{ env.TESTARGS }}
338+
330339
- name: Test with unstable features
331340
if: ${{ !matrix.dinghy && matrix.rust.toolchain == 'nightly' }}
332341
uses: actions-rs/cargo@v1

objc2/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ objc2-encode = { path = "../objc2-encode", version = "=2.0.0-beta.2" }
4848
[build-dependencies]
4949
cc = { version = "1", optional = true }
5050

51+
[dev-dependencies]
52+
iai = { version = "0.1", git = "https://github.com/madsmtm/iai", branch = "callgrind" }
53+
54+
[[bench]]
55+
name = "autorelease"
56+
harness = false
57+
5158
[package.metadata.docs.rs]
5259
default-target = "x86_64-apple-darwin"
5360

objc2/benches/autorelease.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use core::ffi::c_void;
2+
use std::mem::ManuallyDrop;
3+
use std::ptr::NonNull;
4+
5+
use objc2::ffi;
6+
use objc2::rc::{autoreleasepool, Id, Shared};
7+
use objc2::runtime::{Class, Object, Sel};
8+
use objc2::{class, msg_send, sel};
9+
10+
#[cfg(apple)]
11+
#[link(name = "Foundation", kind = "framework")]
12+
extern "C" {}
13+
14+
#[cfg(gnustep)]
15+
#[link(name = "gnustep-base", kind = "dylib")]
16+
extern "C" {}
17+
18+
const BYTES: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
19+
20+
fn empty() {
21+
#[cfg(gnustep)]
22+
unsafe {
23+
objc2::__gnustep_hack::get_class_to_force_linkage()
24+
};
25+
}
26+
27+
fn pool_cleanup() {
28+
autoreleasepool(|_| {})
29+
}
30+
31+
fn class() -> &'static Class {
32+
class!(NSObject)
33+
}
34+
35+
fn sel() -> Sel {
36+
sel!(alloc)
37+
}
38+
39+
fn send_message() -> &'static Class {
40+
unsafe { msg_send![class!(NSObject), class] }
41+
}
42+
43+
fn alloc_nsobject() -> *mut Object {
44+
unsafe { msg_send![class!(NSObject), alloc] }
45+
}
46+
47+
fn new_nsobject() -> Id<Object, Shared> {
48+
let obj = alloc_nsobject();
49+
let obj: *mut Object = unsafe { msg_send![obj, init] };
50+
unsafe { Id::new(NonNull::new_unchecked(obj)) }
51+
}
52+
53+
fn new_nsdata() -> Id<Object, Shared> {
54+
let bytes_ptr = BYTES.as_ptr() as *const c_void;
55+
let obj: *mut Object = unsafe { msg_send![class!(NSData), alloc] };
56+
let obj: *mut Object = unsafe {
57+
msg_send![
58+
obj,
59+
initWithBytes: bytes_ptr,
60+
length: BYTES.len(),
61+
]
62+
};
63+
unsafe { Id::new(NonNull::new_unchecked(obj)) }
64+
}
65+
66+
fn new_leaked_nsdata() -> *mut Object {
67+
ManuallyDrop::new(new_nsdata()).as_ptr()
68+
}
69+
70+
fn autoreleased_nsdata() -> *mut Object {
71+
// let bytes_ptr = BYTES.as_ptr() as *const c_void;
72+
// unsafe {
73+
// msg_send![
74+
// class!(NSData),
75+
// dataWithBytes: bytes_ptr,
76+
// length: BYTES.len(),
77+
// ]
78+
// }
79+
unsafe { msg_send![new_leaked_nsdata(), autorelease] }
80+
}
81+
82+
fn new_nsstring() -> Id<Object, Shared> {
83+
let obj: *mut Object = unsafe { msg_send![class!(NSString), alloc] };
84+
let obj: *mut Object = unsafe { msg_send![obj, init] };
85+
unsafe { Id::new(NonNull::new_unchecked(obj)) }
86+
}
87+
88+
fn new_leaked_nsstring() -> *mut Object {
89+
ManuallyDrop::new(new_nsstring()).as_ptr()
90+
}
91+
92+
fn autoreleased_nsstring() -> *mut Object {
93+
// unsafe { msg_send![class!(NSString), string] }
94+
unsafe { msg_send![new_leaked_nsstring(), autorelease] }
95+
}
96+
97+
fn retain_autoreleased(obj: *mut Object) -> Id<Object, Shared> {
98+
let obj = unsafe { ffi::objc_retainAutoreleasedReturnValue(obj.cast()) };
99+
unsafe { Id::new(NonNull::new_unchecked(obj).cast()) }
100+
}
101+
102+
fn autoreleased_nsdata_pool_cleanup() -> *mut Object {
103+
autoreleasepool(|_| autoreleased_nsdata())
104+
}
105+
106+
fn autoreleased_nsdata_fast_caller_cleanup() -> Id<Object, Shared> {
107+
retain_autoreleased(autoreleased_nsdata())
108+
}
109+
110+
fn autoreleased_nsdata_fast_caller_cleanup_pool_cleanup() -> Id<Object, Shared> {
111+
autoreleasepool(|_| retain_autoreleased(autoreleased_nsdata()))
112+
}
113+
114+
fn autoreleased_nsstring_pool_cleanup() -> *mut Object {
115+
autoreleasepool(|_| autoreleased_nsstring())
116+
}
117+
118+
fn autoreleased_nsstring_fast_caller_cleanup() -> Id<Object, Shared> {
119+
retain_autoreleased(autoreleased_nsstring())
120+
}
121+
122+
fn autoreleased_nsstring_fast_caller_cleanup_pool_cleanup() -> Id<Object, Shared> {
123+
autoreleasepool(|_| retain_autoreleased(autoreleased_nsstring()))
124+
}
125+
126+
macro_rules! main_with_warmup {
127+
($($f:ident,)+) => {
128+
mod warmup_fns {
129+
$(
130+
#[inline(never)]
131+
pub fn $f() {
132+
let _ = iai::black_box(super::$f());
133+
}
134+
)+
135+
}
136+
137+
fn warmup() {
138+
$(
139+
warmup_fns::$f();
140+
)+
141+
}
142+
143+
iai::main! {
144+
warmup,
145+
$(
146+
$f,
147+
)+
148+
}
149+
};
150+
}
151+
152+
main_with_warmup! {
153+
// Baseline
154+
empty,
155+
pool_cleanup,
156+
class,
157+
sel,
158+
send_message,
159+
alloc_nsobject,
160+
new_nsobject,
161+
// NSData
162+
new_nsdata,
163+
new_leaked_nsdata,
164+
autoreleased_nsdata,
165+
autoreleased_nsdata_pool_cleanup,
166+
autoreleased_nsdata_fast_caller_cleanup,
167+
autoreleased_nsdata_fast_caller_cleanup_pool_cleanup,
168+
// NSString
169+
new_nsstring,
170+
new_leaked_nsstring,
171+
autoreleased_nsstring,
172+
autoreleased_nsstring_pool_cleanup,
173+
autoreleased_nsstring_fast_caller_cleanup,
174+
autoreleased_nsstring_fast_caller_cleanup_pool_cleanup,
175+
}

objc2/src/rc/autorelease.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ impl AutoreleasePool {
4545
/// Additionally, the pools must be dropped in the same order they were
4646
/// created.
4747
#[doc(alias = "objc_autoreleasePoolPush")]
48+
#[inline]
4849
unsafe fn new() -> Self {
4950
// TODO: Make this function pub when we're more certain of the API
5051
let context = unsafe { ffi::objc_autoreleasePoolPush() };
@@ -57,10 +58,7 @@ impl AutoreleasePool {
5758
}
5859

5960
/// This will be removed in a future version.
60-
#[cfg_attr(
61-
not(all(debug_assertions, not(feature = "unstable_autoreleasesafe"))),
62-
inline
63-
)]
61+
#[inline]
6462
#[doc(hidden)]
6563
pub fn __verify_is_inner(&self) {
6664
#[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))]
@@ -139,6 +137,7 @@ impl Drop for AutoreleasePool {
139137
/// [clang documentation]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#autoreleasepool
140138
/// [revision `371`]: https://github.com/apple-oss-distributions/objc4/blob/objc4-371/runtime/objc-exception.m#L479-L482
141139
#[doc(alias = "objc_autoreleasePoolPop")]
140+
#[inline]
142141
fn drop(&mut self) {
143142
unsafe { ffi::objc_autoreleasePoolPop(self.context) }
144143
#[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))]
@@ -292,6 +291,7 @@ impl !AutoreleaseSafe for AutoreleasePool {}
292291
/// # panic!("Does not panic in release mode, so for testing we make it!");
293292
/// ```
294293
#[doc(alias = "@autoreleasepool")]
294+
#[inline(always)]
295295
pub fn autoreleasepool<T, F>(f: F) -> T
296296
where
297297
for<'p> F: FnOnce(&'p AutoreleasePool) -> T + AutoreleaseSafe,

objc2/src/rc/id.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ impl<T: Message, O: Ownership> Id<T, O> {
237237
// let y: &T = &*retained;
238238
// ```
239239
#[doc(alias = "objc_retain")]
240-
#[cfg_attr(not(debug_assertions), inline)]
240+
#[inline]
241241
pub unsafe fn retain(ptr: NonNull<T>) -> Id<T, O> {
242242
let ptr = ptr.as_ptr() as *mut ffi::objc_object;
243243
// SAFETY: The caller upholds that the pointer is valid
@@ -266,7 +266,7 @@ impl<T: Message, O: Ownership> Id<T, O> {
266266
NonNull::new(ptr).map(|ptr| unsafe { Id::retain(ptr) })
267267
}
268268

269-
#[cfg_attr(not(debug_assertions), inline)]
269+
#[inline]
270270
fn autorelease_inner(self) -> *mut T {
271271
// Note that this (and the actual `autorelease`) is not an associated
272272
// function. This breaks the guideline that smart pointers shouldn't

objc2/src/rc/id_traits.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ pub trait DefaultId {
6565
}
6666

6767
impl<T: DefaultId + ?Sized> Default for Id<T, T::Ownership> {
68+
#[inline]
6869
fn default() -> Self {
6970
T::default_id()
7071
}

objc2/src/rc/weak_id.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use core::convert::TryFrom;
44
use core::fmt;
55
use core::marker::PhantomData;
66
use core::ptr;
7-
use core::ptr::NonNull;
87
use std::panic::{RefUnwindSafe, UnwindSafe};
98

109
use super::{Id, Shared};
@@ -35,6 +34,7 @@ pub struct WeakId<T: ?Sized> {
3534
impl<T: Message> WeakId<T> {
3635
/// Construct a new [`WeakId`] referencing the given shared [`Id`].
3736
#[doc(alias = "objc_initWeak")]
37+
#[inline]
3838
pub fn new(obj: &Id<T, Shared>) -> Self {
3939
// Note that taking `&Id<T, Owned>` would not be safe since that would
4040
// allow loading an `Id<T, Shared>` later on.
@@ -68,7 +68,7 @@ impl<T: Message> WeakId<T> {
6868
pub fn load(&self) -> Option<Id<T, Shared>> {
6969
let ptr: *mut *mut ffi::objc_object = self.inner.get() as _;
7070
let obj = unsafe { ffi::objc_loadWeakRetained(ptr) } as *mut T;
71-
NonNull::new(obj).map(|obj| unsafe { Id::new(obj) })
71+
unsafe { Id::new_null(obj) }
7272
}
7373

7474
// TODO: Add `autorelease(&self) -> Option<&T>` using `objc_loadWeak`?
@@ -77,6 +77,7 @@ impl<T: Message> WeakId<T> {
7777
impl<T: ?Sized> Drop for WeakId<T> {
7878
/// Drops the `WeakId` pointer.
7979
#[doc(alias = "objc_destroyWeak")]
80+
#[inline]
8081
fn drop(&mut self) {
8182
unsafe { ffi::objc_destroyWeak(self.inner.get() as _) }
8283
}
@@ -101,6 +102,7 @@ impl<T: Message> Default for WeakId<T> {
101102
/// Constructs a new `WeakId<T>` that doesn't reference any object.
102103
///
103104
/// Calling [`Self::load`] on the return value always gives [`None`].
105+
#[inline]
104106
fn default() -> Self {
105107
// SAFETY: The pointer is null
106108
unsafe { Self::new_inner(ptr::null_mut()) }
@@ -130,6 +132,7 @@ impl<T: RefUnwindSafe + ?Sized> RefUnwindSafe for WeakId<T> {}
130132
impl<T: RefUnwindSafe + ?Sized> UnwindSafe for WeakId<T> {}
131133

132134
impl<T: Message> From<Id<T, Shared>> for WeakId<T> {
135+
#[inline]
133136
fn from(obj: Id<T, Shared>) -> Self {
134137
WeakId::new(&obj)
135138
}

0 commit comments

Comments
 (0)