Skip to content

Commit 34a5046

Browse files
Add CowStr type and modify benchmarks to use CowStr for comparison
1 parent a2b668b commit 34a5046

File tree

5 files changed

+368
-6
lines changed

5 files changed

+368
-6
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ features = [
9191
targets = ["i686-unknown-linux-gnu"]
9292
rustdoc-args = ["--cfg", "docsrs"]
9393

94+
[[bench]]
95+
name = "cow_str"
96+
harness = false
97+
98+
9499
[[bench]]
95100
name = "string_baseline"
96101
harness = false

benches/cow_str.rs

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

benches/string_baseline.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@
44
//! String operations to establish a baseline for comparison.
55
66
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
7-
use heapless::String;
7+
use heapless::{cow::CowStr, String};
88

99
fn create_test_string<const N: usize>(content: &str) -> String<N> {
1010
String::try_from(content).expect("String too long for capacity")
1111
}
1212

1313
/// 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()
14+
fn baseline_always_copy<const N: usize>(input: &String<N>) -> CowStr<'_, N> {
15+
CowStr::Owned(input.clone())
1616
}
1717

1818
/// Function that sometimes mutates the string (requires clone).
1919
fn baseline_conditional_mutation<const N: usize>(
2020
input: &String<N>,
2121
needs_mutation: bool,
22-
) -> String<N> {
22+
) -> CowStr<'_, N> {
2323
if needs_mutation {
2424
let mut owned = input.clone();
2525
let _ = owned.push_str("_mutated");
26-
owned
26+
CowStr::Owned(owned)
2727
} else {
28-
input.clone()
28+
CowStr::Borrowed(input.as_view())
2929
}
3030
}
3131

0 commit comments

Comments
 (0)