Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,18 @@ harness = false
name = "eq"
harness = false

[[bench]]
name = "len"
harness = false

[[bench]]
name = "collection"
harness = false

[[bench]]
name = "reference"
harness = false

[[bench]]
name = "contention"
harness = false
3 changes: 2 additions & 1 deletion bench/benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use criterion::{
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::hint::black_box;
use std::str::FromStr;

use bench::*;
Expand Down
3 changes: 2 additions & 1 deletion bench/benches/collection.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use ahash::AHashSet;
use bench::*;
use criterion::{
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
};
use std::hash::Hash;
use std::hint::black_box;
use std::str::FromStr;

const LENGTHS: &[usize] = &[64];
Expand Down
79 changes: 79 additions & 0 deletions bench/benches/contention.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use bench::*;
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, BenchmarkId, Criterion,
};
use std::hint::black_box;
use std::str::FromStr;
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Instant;

fn bench_string_type<T>(group: &mut BenchmarkGroup<WallTime>, type_name: &str)
where
T: FromStr + Clone + Send + Sync,
{
let threads = 4;

// Dial for contention:
// 1024 = Light (very few atomic collisions)
// 16 = Medium (frequent collisions)
// 1 = Heavy (4 cores hammering the exact same atomic counter)
for pool_size in [1024, 16, 1] {
group.bench_with_input(
BenchmarkId::new(type_name, pool_size),
&pool_size,
|b, &size| {
let pool: Vec<T> = (0..size)
.map(|i| {
T::from_str(&format!("test_string_data_for_index_{:010}", i))
.map_err(|_| ())
.unwrap()
})
.collect();
let pool = Arc::new(pool);
let mask = size - 1;

b.iter_custom(|iters| {
let barrier = Arc::new(Barrier::new(threads + 1));

thread::scope(|s| {
for t in 0..threads {
let pool_ref = pool.clone();
let b = barrier.clone();

s.spawn(move || {
let mut idx = t;
b.wait();

for _ in 0..iters {
idx = (idx + 1) & mask;
let cloned = black_box(pool_ref[idx].clone());
drop(black_box(cloned));
}

b.wait();
});
}

barrier.wait();
let start = Instant::now();
barrier.wait();
start.elapsed()
})
});
},
);
}
}

fn bench_contention(c: &mut Criterion) {
let mut group = c.benchmark_group("Clone Drop Contention");
bench_string_type::<cold_string::ArcColdString>(&mut group, "cold_string::ArcColdString");
bench_string_type::<cold_string::ArcColdString32>(&mut group, "cold_string::ArcColdString32");
bench_string_type::<arcstr::ArcStr>(&mut group, "arcstr::ArcStr");
bench_string_type::<StdArcStr>(&mut group, "Arc<str>");
group.finish();
}

criterion_group!(benches, bench_contention);
criterion_main!(benches);
22 changes: 17 additions & 5 deletions bench/benches/eq.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use std::fmt::Debug;
use std::hint::black_box;
use std::str::FromStr;

use bench::*;
use cold_string::ColdString;
Expand All @@ -9,7 +12,8 @@ const RATIOS: &[f64] = &[0.0, 0.5, 1.0];

fn build_pairs<T>(len: usize, eq_ratio: f64) -> (Vec<T>, Vec<T>)
where
T: From<String>,
T: FromStr,
<T as FromStr>::Err: Debug,
{
let mut left_strings = Vec::with_capacity(COUNT);
let mut right_strings = Vec::with_capacity(COUNT);
Expand All @@ -26,15 +30,22 @@ where
}
}

let left = left_strings.into_iter().map(T::from).collect();
let right = right_strings.into_iter().map(T::from).collect();
let left = left_strings
.into_iter()
.map(|s| s.parse().unwrap())
.collect();
let right = right_strings
.into_iter()
.map(|s| s.parse().unwrap())
.collect();

(left, right)
}

fn bench_eq_type<T>(c: &mut Criterion, name: &str)
where
T: From<String> + PartialEq,
T: FromStr + PartialEq,
<T as FromStr>::Err: Debug,
{
let mut group = c.benchmark_group(name);

Expand All @@ -61,6 +72,7 @@ where

fn bench_eq(c: &mut Criterion) {
bench_eq_type::<ColdString>(c, "ColdString_eq");
bench_eq_type::<compact_string::CompactString>(c, "CompactString_eq");
bench_eq_type::<String>(c, "String_eq");
}

Expand Down
29 changes: 29 additions & 0 deletions bench/benches/len.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use bench::*;
use cold_string::ColdString;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use std::fmt::Debug;
use std::hint::black_box;
use std::str::FromStr;

const LENGTHS: &[usize] = &[4, 8, 16, 255];

macro_rules! bench_len_type {
($c:expr, $name:expr, $t:ty) => {{
let mut group = $c.benchmark_group($name);
for &len in LENGTHS {
let s: $t = random_string::<String>(len, len).parse().unwrap();
let id = BenchmarkId::new(format!("len={}", len), "");
group.bench_with_input(id, &len, |b, _| b.iter(|| black_box(s.len())));
}
group.finish();
}};
}

fn bench_eq(c: &mut Criterion) {
bench_len_type!(c, "ColdString_len", ColdString);
bench_len_type!(c, "CompactString_len", compact_string::CompactString);
bench_len_type!(c, "String_len", String);
}

criterion_group!(benches, bench_eq);
criterion_main!(benches);
5 changes: 3 additions & 2 deletions bench/benches/reference.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::hint::black_box;
use std::sync::Arc;

use arcstr::ArcStr;
use cold_string::ArcColdString;
use criterion::{
black_box, criterion_group, criterion_main, measurement::WallTime, BatchSize, BenchmarkGroup,
BenchmarkId, Criterion,
criterion_group, criterion_main, measurement::WallTime, BatchSize, BenchmarkGroup, BenchmarkId,
Criterion,
};

const LENGTHS: &[usize] = &[16, 128, 512];
Expand Down
105 changes: 105 additions & 0 deletions bench/contention.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import json
import os
import sys
from collections import defaultdict

def generate_contention_table(bench_group):
# Path relative to workspace root
base_path = os.path.join("target", "criterion", bench_group)

if not os.path.exists(base_path):
print(f"Error: Directory '{base_path}' not found.")
print("Make sure you ran `cargo bench` first!")
return

# data[crate_name][pool_size] = point_estimate_in_ns
data = defaultdict(dict)
crates = set()
pool_sizes = set()

for crate in os.listdir(base_path):
crate_path = os.path.join(base_path, crate)

# Skip files or the criterion 'report' folder
if not os.path.isdir(crate_path) or crate == "report":
continue

for size_str in os.listdir(crate_path):
size_path = os.path.join(crate_path, size_str)
if not os.path.isdir(size_path):
continue

try:
pool_size = int(size_str)
estimates_path = os.path.join(size_path, "new", "estimates.json")

if os.path.exists(estimates_path):
with open(estimates_path, 'r') as f:
est = json.load(f)
# point_estimate is automatically calculated by Criterion as ns/iter
nanos_per_iter = est["mean"]["point_estimate"]

data[crate][pool_size] = nanos_per_iter
crates.add(crate)
pool_sizes.add(pool_size)
except ValueError:
# size_str wasn't an integer, skip
continue
except Exception as e:
print(f"Error reading {estimates_path}: {e}")
continue

if not crates:
print("No benchmark data found in the directory.")
return

# Sort sizes descending so columns appear as: 1024 (Light), 16 (Medium), 1 (Heavy)
sorted_sizes = sorted(list(pool_sizes), reverse=True)
sorted_crates = sorted(list(crates))

def get_size_label(s):
if s == 1024: return "Light (1024)"
if s == 16: return "Medium (16)"
if s == 1: return "Heavy (1)"
return f"Size ({s})"

# Criterion sanitizes folder names (e.g., replaces < > with _ on Windows/Linux)
# This cleans up the display name for the markdown table
def clean_crate_name(name):
return name.replace("Arc_str_", "Arc<str>")

# Calculate padding dynamically based on the longest crate name
max_crate_len = max([len(clean_crate_name(c)) for c in sorted_crates] + [18])
cell_width = 14

print(f"### {bench_group} [ns/iter]")

# --- Header Row ---
header_cols = [f"{'Type':<{max_crate_len}}"] + [get_size_label(s).center(cell_width) for s in sorted_sizes]
print(" | ".join(header_cols))

# --- Markdown Separator Row ---
sep_cols = [f"{':---':<{max_crate_len}}"] + [f"{':---:':^{cell_width}}" for _ in sorted_sizes]
print(" | ".join(sep_cols))

# --- Data Rows ---
for crate in sorted_crates:
display_name = clean_crate_name(crate)
row_cells = [f"{display_name:<{max_crate_len}}"]

for s in sorted_sizes:
val = data[crate].get(s)
if val is not None:
# Format to 2 decimal places
row_cells.append(f"{val:{cell_width}.2f}")
else:
row_cells.append(f"{'-':^{cell_width}}")

print(" | ".join(row_cells))
print()

if __name__ == "__main__":
# If using the Criterion name from the previous rust code:
# python report_contention.py "Clone Drop Contention"
group = sys.argv[1] if len(sys.argv) > 1 else "Clone Drop Contention"
generate_contention_table(group)
Loading
Loading