Skip to content

Commit ea3bd5b

Browse files
Add CowStr benchmarks comparing against String baseline
1 parent be19eac commit ea3bd5b

File tree

2 files changed

+270
-0
lines changed

2 files changed

+270
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ criterion = "0.5"
8181
name = "string_baseline"
8282
harness = false
8383

84+
[[bench]]
85+
name = "cow_str"
86+
harness = false
87+
8488
[package.metadata.docs.rs]
8589
features = [
8690
"bytes",

benches/cow_str.rs

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
//! Criterion benchmarks for `CowStr` enum.
2+
//!
3+
//! This benchmark suite measures the performance characteristics of `CowStr`
4+
//! and compares it against baseline implementations that always copy strings.
5+
6+
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
7+
use heapless::{cow::CowStr, String};
8+
9+
fn create_test_string<const N: usize>(content: &str) -> String<N> {
10+
String::try_from(content).expect("String too long for capacity")
11+
}
12+
13+
/// Baseline function that always copies into a new String<N>.
14+
fn baseline_always_copy<const N: usize>(input: &String<N>) -> String<N> {
15+
input.clone()
16+
}
17+
18+
/// Function returning CowStr that only clones on mutation.
19+
fn cowstr_clone_on_mutation<'a, const N: usize>(
20+
input: &'a String<N>,
21+
needs_mutation: bool,
22+
) -> CowStr<'a, N> {
23+
if needs_mutation {
24+
let mut owned = input.clone();
25+
owned.push_str("_mutated").ok();
26+
CowStr::Owned(owned)
27+
} else {
28+
CowStr::Borrowed(input.as_view())
29+
}
30+
}
31+
32+
fn bench_baseline_vs_cowstr(c: &mut Criterion) {
33+
let mut group = c.benchmark_group("baseline_vs_cowstr");
34+
35+
let test_str: String<128> = create_test_string("test string for comparison");
36+
37+
group.bench_function("baseline_always_copy", |b| {
38+
b.iter(|| {
39+
let result = baseline_always_copy(black_box(&test_str));
40+
black_box(result)
41+
});
42+
});
43+
44+
group.bench_function("cowstr_no_mutation", |b| {
45+
b.iter(|| {
46+
let result = cowstr_clone_on_mutation(black_box(&test_str), false);
47+
black_box(result)
48+
});
49+
});
50+
51+
group.bench_function("cowstr_with_mutation", |b| {
52+
b.iter(|| {
53+
let result = cowstr_clone_on_mutation(black_box(&test_str), true);
54+
black_box(result)
55+
});
56+
});
57+
58+
group.finish();
59+
}
60+
61+
fn bench_cowstr_creation(c: &mut Criterion) {
62+
let mut group = c.benchmark_group("cowstr_creation");
63+
64+
let short_str: String<16> = create_test_string("hello");
65+
let medium_str: String<64> = create_test_string("This is a medium length test string");
66+
let long_str: String<256> = create_test_string("This is a much longer test string that contains more characters to properly test the performance characteristics of the CowStr type with different sizes");
67+
68+
group.bench_function("borrowed_short", |b| {
69+
b.iter(|| {
70+
let view = black_box(&short_str).as_view();
71+
black_box(CowStr::<16>::Borrowed(view))
72+
});
73+
});
74+
75+
group.bench_function("borrowed_medium", |b| {
76+
b.iter(|| {
77+
let view = black_box(&medium_str).as_view();
78+
black_box(CowStr::<64>::Borrowed(view))
79+
});
80+
});
81+
82+
group.bench_function("borrowed_long", |b| {
83+
b.iter(|| {
84+
let view = black_box(&long_str).as_view();
85+
black_box(CowStr::<256>::Borrowed(view))
86+
});
87+
});
88+
89+
group.bench_function("owned_short", |b| {
90+
b.iter(|| {
91+
let s = black_box(short_str.clone());
92+
black_box(CowStr::<16>::Owned(s))
93+
});
94+
});
95+
96+
group.bench_function("owned_medium", |b| {
97+
b.iter(|| {
98+
let s = black_box(medium_str.clone());
99+
black_box(CowStr::<64>::Owned(s))
100+
});
101+
});
102+
103+
group.bench_function("owned_long", |b| {
104+
b.iter(|| {
105+
let s = black_box(long_str.clone());
106+
black_box(CowStr::<256>::Owned(s))
107+
});
108+
});
109+
110+
group.finish();
111+
}
112+
113+
fn bench_cowstr_as_str(c: &mut Criterion) {
114+
let mut group = c.benchmark_group("cowstr_as_str");
115+
116+
let test_str: String<64> = create_test_string("test string for as_str");
117+
let borrowed = CowStr::<64>::Borrowed(test_str.as_view());
118+
let owned = CowStr::<64>::Owned(test_str.clone());
119+
let static_string: &'static String<64> = Box::leak(Box::new(test_str.clone()));
120+
let static_cow = CowStr::<64>::Static(static_string.as_view());
121+
122+
group.bench_function("borrowed", |b| {
123+
b.iter(|| black_box(&borrowed).as_str());
124+
});
125+
126+
group.bench_function("static", |b| {
127+
b.iter(|| black_box(&static_cow).as_str());
128+
});
129+
130+
group.bench_function("owned", |b| {
131+
b.iter(|| black_box(&owned).as_str());
132+
});
133+
134+
group.finish();
135+
}
136+
137+
fn bench_cowstr_to_owned_for_size<const N: usize>(
138+
group: &mut criterion::BenchmarkGroup<criterion::measurement::WallTime>,
139+
content: &str,
140+
size: usize,
141+
) {
142+
let test_str: String<N> = create_test_string(content);
143+
let borrowed = CowStr::<N>::Borrowed(test_str.as_view());
144+
let owned = CowStr::<N>::Owned(test_str.clone());
145+
146+
group.bench_with_input(BenchmarkId::new("borrowed", size), &size, |b, _| {
147+
b.iter(|| black_box(&borrowed).to_owned());
148+
});
149+
150+
group.bench_with_input(BenchmarkId::new("owned", size), &size, |b, _| {
151+
b.iter(|| black_box(&owned).to_owned());
152+
});
153+
}
154+
155+
fn bench_cowstr_to_owned(c: &mut Criterion) {
156+
let mut group = c.benchmark_group("cowstr_to_owned");
157+
158+
bench_cowstr_to_owned_for_size::<16>(&mut group, "short", 16);
159+
bench_cowstr_to_owned_for_size::<64>(
160+
&mut group,
161+
"This is a medium length string for testing",
162+
64,
163+
);
164+
bench_cowstr_to_owned_for_size::<256>(
165+
&mut group,
166+
"This is a very long string that we use to test the performance of the to_owned method on CowStr with different string sizes and capacities to ensure proper benchmarking",
167+
256,
168+
);
169+
170+
group.finish();
171+
}
172+
173+
fn bench_cowstr_type_checks(c: &mut Criterion) {
174+
let mut group = c.benchmark_group("cowstr_type_checks");
175+
176+
let test_str: String<64> = create_test_string("test");
177+
let borrowed = CowStr::<64>::Borrowed(test_str.as_view());
178+
let owned = CowStr::<64>::Owned(test_str.clone());
179+
let static_string: &'static String<64> = Box::leak(Box::new(test_str.clone()));
180+
let static_cow = CowStr::<64>::Static(static_string.as_view());
181+
182+
group.bench_function("is_borrowed", |b| {
183+
b.iter(|| black_box(&borrowed).is_borrowed());
184+
});
185+
186+
group.bench_function("is_static", |b| {
187+
b.iter(|| black_box(&static_cow).is_static());
188+
});
189+
190+
group.bench_function("is_owned", |b| {
191+
b.iter(|| black_box(&owned).is_owned());
192+
});
193+
194+
group.finish();
195+
}
196+
197+
fn bench_cowstr_vs_clone(c: &mut Criterion) {
198+
let mut group = c.benchmark_group("cowstr_vs_clone");
199+
200+
let test_str: String<128> =
201+
create_test_string("This is a string that would normally be cloned in every operation");
202+
203+
group.bench_function("cowstr_borrowed_read_only", |b| {
204+
b.iter(|| {
205+
let cow = CowStr::<128>::Borrowed(black_box(&test_str).as_view());
206+
let _result = black_box(cow.as_str());
207+
});
208+
});
209+
210+
group.bench_function("string_clone_read_only", |b| {
211+
b.iter(|| {
212+
let s = black_box(&test_str).clone();
213+
let _result = black_box(s.as_str());
214+
});
215+
});
216+
217+
group.bench_function("cowstr_to_owned_when_needed", |b| {
218+
b.iter(|| {
219+
let cow = CowStr::<128>::Borrowed(black_box(&test_str).as_view());
220+
let _owned = black_box(cow.to_owned());
221+
});
222+
});
223+
224+
group.bench_function("string_clone_when_needed", |b| {
225+
b.iter(|| {
226+
let s = black_box(&test_str).clone();
227+
let _owned = black_box(s);
228+
});
229+
});
230+
231+
group.finish();
232+
}
233+
234+
fn bench_cowstr_from(c: &mut Criterion) {
235+
let mut group = c.benchmark_group("cowstr_from");
236+
237+
let test_str: String<64> = create_test_string("test string");
238+
239+
group.bench_function("from_stringview", |b| {
240+
b.iter(|| {
241+
let view = black_box(&test_str).as_view();
242+
black_box(CowStr::<64>::from(view))
243+
});
244+
});
245+
246+
group.bench_function("from_string", |b| {
247+
b.iter(|| {
248+
let s = black_box(&test_str).clone();
249+
black_box(CowStr::<64>::from(s))
250+
});
251+
});
252+
253+
group.finish();
254+
}
255+
256+
criterion_group!(
257+
benches,
258+
bench_baseline_vs_cowstr,
259+
bench_cowstr_creation,
260+
bench_cowstr_as_str,
261+
bench_cowstr_to_owned,
262+
bench_cowstr_type_checks,
263+
bench_cowstr_vs_clone,
264+
bench_cowstr_from
265+
);
266+
criterion_main!(benches);

0 commit comments

Comments
 (0)