Skip to content
This repository was archived by the owner on Nov 9, 2025. It is now read-only.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ all-features = true
serde_core = { version = "1.0.220", optional = true, default-features = false }
borsh = { version = "1.4.0", optional = true, default-features = false }
arbitrary = { version = "1.3", optional = true }
bincode = "2.0"

[dev-dependencies]
proptest = "1.5"
Expand Down
64 changes: 60 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,66 @@ whitespace are a typical pattern in computer programs because of indentation.
Note that a specialized interner might be a better solution for some use cases.

## Benchmarks
Run criterion benches with
```sh
cargo bench --bench \* -- --quick
```

The following benchmarks illustrate the performance characteristics of `SmolStr` for various operations. All benchmarks were run on `Monday, December 1, 2025`.

### `from_utf8_lossy` Comparison: `SmolStr` vs `String`

This section compares `SmolStr::from_utf8_lossy` against `String::from_utf8_lossy` for different string lengths and validity scenarios. The percentage difference indicates how much slower (+) or faster (-) `SmolStr` is compared to `String`.

| Length | Scenario | SmolStr Time (ns) | String Time (ns) | SmolStr vs String |
|--------|--------------------|-------------------|------------------|-------------------|
| 12 | Valid | 13.068 | 10.154 | +28.69% slower |
| 12 | Invalid (single) | 13.432 | 23.700 | -43.32% faster |
| 12 | Invalid (many) | 25.038 | 35.990 | -30.43% faster |
| 50 | Valid | 43.395 | 28.802 | +50.66% slower |
| 50 | Invalid (single) | 73.348 | 57.962 | +26.54% slower |
| 50 | Invalid (many) | 107.27 | 91.219 | +17.59% slower |
| 1000 | Valid | 268.77 | 240.27 | +11.86% slower |
| 1000 | Invalid (single) | 354.94 | 322.10 | +10.19% slower |
| 1000 | Invalid (many) | 1424.3 | 1328.6 | +7.20% slower |

_Note: Negative percentage indicates SmolStr is faster, positive indicates SmolStr is slower._

### Other `SmolStr` Operations

Here are the detailed benchmark results for other `SmolStr` operations, organized by string length:

#### Length: 12 bytes

| Benchmark | Time (ns) |
|------------------------------------------|-----------|
| `format_smolstr!` | 26.389 |
| `SmolStr::from` | 14.389 |
| `SmolStr::clone` | 4.3895 |
| `SmolStr::eq` | 2.2177 |
| `to_lowercase_smolstr` | 23.447 |
| `to_ascii_lowercase_smolstr` | 7.4287 |
| `replace_smolstr` | 8.0733 |

#### Length: 50 bytes

| Benchmark | Time (ns) |
|------------------------------------------|-----------|
| `format_smolstr!` | 59.060 |
| `SmolStr::from` | 12.080 |
| `SmolStr::clone` | 3.6731 |
| `SmolStr::eq` | 2.3987 |
| `to_lowercase_smolstr` | 51.157 |
| `to_ascii_lowercase_smolstr` | 26.337 |
| `replace_smolstr` | 33.498 |

#### Length: 1000 bytes

| Benchmark | Time (ns) |
|------------------------------------------|-----------|
| `format_smolstr!` | 101.73 |
| `SmolStr::from` | 19.590 |
| `SmolStr::clone` | 3.2027 |
| `SmolStr::eq` | 11.466 |
| `to_lowercase_smolstr` | 146.04 |
| `to_ascii_lowercase_smolstr` | 64.224 |
| `replace_smolstr` | 212.61 |

## MSRV Policy

Expand Down
72 changes: 72 additions & 0 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,77 @@ fn replace_bench(c: &mut Criterion) {
}
}

fn from_utf8_lossy_bench(c: &mut Criterion) {
let mut group = c.benchmark_group("from_utf8_lossy");

for len in TEST_LENS {
// Valid UTF-8
let valid_bytes = Alphanumeric
.sample_string(&mut rand::rng(), len)
.into_bytes();
group.bench_with_input(
format!("SmolStr_valid_len={}", len),
&valid_bytes,
|b, bytes| {
b.iter(|| SmolStr::from_utf8_lossy(black_box(bytes)));
},
);
group.bench_with_input(
format!("String_valid_len={}", len),
&valid_bytes,
|b, bytes| {
b.iter(|| String::from_utf8_lossy(black_box(bytes)));
},
);

// Invalid UTF-8 (single invalid byte)
let mut invalid_bytes_single = Alphanumeric
.sample_string(&mut rand::rng(), len - 1)
.into_bytes();
invalid_bytes_single.push(0xFF);
group.bench_with_input(
format!("SmolStr_invalid_single_len={}", len),
&invalid_bytes_single,
|b, bytes| {
b.iter(|| SmolStr::from_utf8_lossy(black_box(bytes)));
},
);
group.bench_with_input(
format!("String_invalid_single_len={}", len),
&invalid_bytes_single,
|b, bytes| {
b.iter(|| String::from_utf8_lossy(black_box(bytes)));
},
);

// Invalid UTF-8 (many invalid bytes)
let mut invalid_bytes_many = Vec::with_capacity(len);
for i in 0..len {
if i % 5 == 0 {
invalid_bytes_many.push(0xFF); // Invalid byte
} else {
invalid_bytes_many
.push(Alphanumeric.sample_string(&mut rand::rng(), 1).as_bytes()[0]);
}
}
group.bench_with_input(
format!("SmolStr_invalid_many_len={}", len),
&invalid_bytes_many,
|b, bytes| {
b.iter(|| SmolStr::from_utf8_lossy(black_box(bytes)));
},
);
group.bench_with_input(
format!("String_invalid_many_len={}", len),
&invalid_bytes_many,
|b, bytes| {
b.iter(|| String::from_utf8_lossy(black_box(bytes)));
},
);
}
group.finish();
}

criterion_group!(
benches,
format_bench,
Expand All @@ -114,5 +185,6 @@ criterion_group!(
to_lowercase_bench,
to_ascii_lowercase_bench,
replace_bench,
from_utf8_lossy_bench,
);
criterion_main!(benches);
Loading