From ea313e8828864b7be88fe325396ba5be392bd90f Mon Sep 17 00:00:00 2001 From: Thomas Pendock Date: Wed, 30 Sep 2026 16:16:54 -0700 Subject: [PATCH 1/2] add contention to benches --- bench/Cargo.toml | 4 + bench/benches/bench.rs | 3 +- bench/benches/collection.rs | 3 +- bench/benches/contention.rs | 79 ++++++++++++++++++++ bench/benches/eq.rs | 3 +- bench/benches/reference.rs | 5 +- bench/contention.py | 105 +++++++++++++++++++++++++++ bench/examples/reference_memory.rs | 113 ----------------------------- bench/src/lib.rs | 13 ++++ bench/tests/memory.rs | 67 ++++++++++------- cold-string/README.md | 27 +++++-- 11 files changed, 272 insertions(+), 150 deletions(-) create mode 100644 bench/benches/contention.rs create mode 100644 bench/contention.py delete mode 100644 bench/examples/reference_memory.rs diff --git a/bench/Cargo.toml b/bench/Cargo.toml index ef0fdd0..0ea0905 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -35,3 +35,7 @@ harness = false [[bench]] name = "reference" harness = false + +[[bench]] +name = "contention" +harness = false \ No newline at end of file diff --git a/bench/benches/bench.rs b/bench/benches/bench.rs index 254a9c5..a088613 100644 --- a/bench/benches/bench.rs +++ b/bench/benches/bench.rs @@ -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::*; diff --git a/bench/benches/collection.rs b/bench/benches/collection.rs index e461d68..aa3b5c4 100644 --- a/bench/benches/collection.rs +++ b/bench/benches/collection.rs @@ -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]; diff --git a/bench/benches/contention.rs b/bench/benches/contention.rs new file mode 100644 index 0000000..b605b06 --- /dev/null +++ b/bench/benches/contention.rs @@ -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(group: &mut BenchmarkGroup, 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 = (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::(&mut group, "cold_string::ArcColdString"); + bench_string_type::(&mut group, "cold_string::ArcColdString32"); + bench_string_type::(&mut group, "arcstr::ArcStr"); + bench_string_type::(&mut group, "Arc"); + group.finish(); +} + +criterion_group!(benches, bench_contention); +criterion_main!(benches); diff --git a/bench/benches/eq.rs b/bench/benches/eq.rs index 6fb8084..87d54a4 100644 --- a/bench/benches/eq.rs +++ b/bench/benches/eq.rs @@ -1,4 +1,5 @@ -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use std::hint::black_box; use bench::*; use cold_string::ColdString; diff --git a/bench/benches/reference.rs b/bench/benches/reference.rs index 3c00089..4b8bc3b 100644 --- a/bench/benches/reference.rs +++ b/bench/benches/reference.rs @@ -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]; diff --git a/bench/contention.py b/bench/contention.py new file mode 100644 index 0000000..7b549e2 --- /dev/null +++ b/bench/contention.py @@ -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") + + # 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) \ No newline at end of file diff --git a/bench/examples/reference_memory.rs b/bench/examples/reference_memory.rs deleted file mode 100644 index 4746860..0000000 --- a/bench/examples/reference_memory.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::{env, hint::black_box, process::Command, str}; - -use arcstr::ArcStr; -use cold_string::{ArcColdString, ArcColdString32}; -use sysinfo::{Pid, ProcessesToUpdate, System}; - -const LENGTHS: &[usize] = &[8, 128, 512]; -const KINDS: &[&str] = &["Arc", "ArcStr", "ArcColdString", "ArcColdString32"]; -const ALPHABET: &[u8; 62] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - -fn write_unique_suffix(buf: &mut [u8], mut value: usize) { - for byte in buf.iter_mut().rev().take(8) { - *byte = ALPHABET[value % ALPHABET.len()]; - value /= ALPHABET.len(); - } - assert_eq!(value, 0, "item count exceeds the unique suffix range"); -} - -fn rss(system: &mut System, pid: Pid) -> u64 { - system.refresh_processes(ProcessesToUpdate::Some(&[pid]), false); - system.process(pid).unwrap().memory() -} - -fn measure(len: usize, count: usize) -> u64 -where - for<'a> T: From<&'a str>, -{ - let mut scratch = vec![b'x'; len]; - let pid = Pid::from(std::process::id() as usize); - let mut system = System::new_all(); - let baseline = rss(&mut system, pid); - - let mut strings = Vec::::with_capacity(count); - for i in 0..count { - write_unique_suffix(&mut scratch, i); - let value = unsafe { str::from_utf8_unchecked(&scratch) }; - strings.push(T::from(value)); - } - black_box(&strings); - - let used = rss(&mut system, pid).saturating_sub(baseline); - std::mem::forget(strings); - used -} - -fn child(kind: &str, len: usize, count: usize) { - let used = match kind { - "Arc" => measure::>(len, count), - "ArcStr" => measure::(len, count), - "ArcColdString" => measure::(len, count), - "ArcColdString32" => measure::(len, count), - _ => panic!("unknown string kind: {kind}"), - }; - println!("{used}"); -} - -fn sample(kind: &str, len: usize, count: usize) -> u64 { - let output = Command::new(env::current_exe().unwrap()) - .args(["--child", kind, &len.to_string(), &count.to_string()]) - .output() - .unwrap(); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - str::from_utf8(&output.stdout) - .unwrap() - .trim() - .parse() - .unwrap() -} - -fn main() { - let args: Vec<_> = env::args().collect(); - if args.get(1).map(String::as_str) == Some("--child") { - child(&args[2], args[3].parse().unwrap(), args[4].parse().unwrap()); - return; - } - - let count = env::var("COLD_STRING_MEMORY_COUNT") - .ok() - .map(|value| value.parse().unwrap()) - .unwrap_or(1_000_000); - let trials = env::var("COLD_STRING_MEMORY_TRIALS") - .ok() - .map(|value| value.parse().unwrap()) - .unwrap_or(3); - assert!(count > 0 && trials > 0); - - println!("RSS bytes per unique string in Vec ({count} strings, median of {trials} runs)"); - print!("| Type |"); - for len in LENGTHS { - print!(" {len} bytes |"); - } - println!(); - print!("|:--|"); - for _ in LENGTHS { - print!("--:|"); - } - println!(); - - for kind in KINDS { - print!("| {kind} |"); - for &len in LENGTHS { - let mut samples: Vec<_> = (0..trials).map(|_| sample(kind, len, count)).collect(); - samples.sort_unstable(); - let bytes_per_string = samples[trials / 2] as f64 / count as f64; - print!(" {bytes_per_string:.1} |"); - } - println!(); - } -} diff --git a/bench/src/lib.rs b/bench/src/lib.rs index 83d5fcf..f33b9f2 100644 --- a/bench/src/lib.rs +++ b/bench/src/lib.rs @@ -1,4 +1,17 @@ use std::str::FromStr; +use std::sync::Arc; + +#[derive(Clone, Hash, Eq, PartialEq)] +#[repr(transparent)] +pub struct StdArcStr(Arc); + +impl FromStr for StdArcStr { + type Err = std::convert::Infallible; + #[inline] + fn from_str(s: &str) -> Result { + Ok(Self(Arc::::from(s))) + } +} pub fn random_string(min: usize, max: usize) -> T { let len = fastrand::usize(min..=max); diff --git a/bench/tests/memory.rs b/bench/tests/memory.rs index dc07c42..fc4f792 100644 --- a/bench/tests/memory.rs +++ b/bench/tests/memory.rs @@ -121,12 +121,13 @@ impl FromStr for Data { } const SIZES: &[usize] = &[4, 8, 16, 32, 64]; +const MIN_SIZE: usize = 0; const CELL_WIDTH: usize = 7; -const NAME_WIDTH: usize = 16; +const NAME_WIDTH: usize = 32; const TRIALS: usize = 1_000_000; fn system_memory(name: &str, workload: impl Fn(usize, usize)) { - print!("{:CELL_WIDTH$}", format!("{}..={}", 0, size)); + for &max in SIZES { + let min = std::cmp::min(MIN_SIZE, max); + print!(" | {:>CELL_WIDTH$}", format!("{}..={}", min, max)); } println!(); @@ -168,13 +171,13 @@ fn print_table_header(title: &str) { #[ignore] fn test_system_memory_vec() { print_table_header("Vec"); - system_memory("cold-string", vec_workload::); - system_memory("compact_str", vec_workload::); - system_memory("compact_string", vec_workload::); - system_memory("smallstr", vec_workload::>); - system_memory("smartstring", vec_workload::); - system_memory("smol_str", vec_workload::); - system_memory("std", vec_workload::); + system_memory("cold_string::ColdString", vec_workload::); + system_memory("compact_str::CompactString", vec_workload::); + system_memory("compact_string::CompactString", vec_workload::); + system_memory("smallstr::SmallString<[u8; 8]>", vec_workload::>); + system_memory("smartstring::alias::String", vec_workload::); + system_memory("smol_str::SmolStr", vec_workload::); + system_memory("String", vec_workload::); } /// `cargo test test_system_memory_hashset --release -- --no-capture --include-ignored` @@ -183,13 +186,13 @@ fn test_system_memory_vec() { #[ignore] fn test_system_memory_hashset() { print_table_header("HashSet"); - system_memory("cold-string", hash_set_workload::); - system_memory("compact_str", hash_set_workload::); - system_memory("compact_string", hash_set_workload::); - system_memory("smallstr", hash_set_workload::>); - system_memory("smartstring", hash_set_workload::); - system_memory("smol_str", hash_set_workload::); - system_memory("std", hash_set_workload::); + system_memory("cold_string::ColdString", hash_set_workload::); + system_memory("compact_str::CompactString", hash_set_workload::); + system_memory("compact_string::CompactString", hash_set_workload::); + system_memory("smallstr::SmallString<[u8; 8]>", hash_set_workload::>); + system_memory("smartstring::alias::String", hash_set_workload::); + system_memory("smol_str::SmolStr", hash_set_workload::); + system_memory("String", hash_set_workload::); } /// `cargo test test_system_memory_btreeset --release -- --no-capture --include-ignored` @@ -198,11 +201,23 @@ fn test_system_memory_hashset() { #[ignore] fn test_system_memory_btreeset() { print_table_header("BTreeSet"); - system_memory("cold-string", btree_workload::); - system_memory("compact_str", btree_workload::); - system_memory("compact_string", btree_workload::); - system_memory("smallstr", btree_workload::>); - system_memory("smartstring", btree_workload::); - system_memory("smol_str", btree_workload::); - system_memory("std", btree_workload::); + system_memory("cold_string::ColdString", btree_workload::); + system_memory("compact_str::CompactString", btree_workload::); + system_memory("compact_string::CompactString", btree_workload::); + system_memory("smallstr::SmallString<[u8; 8]>", btree_workload::>); + system_memory("smartstring::alias::String", btree_workload::); + system_memory("smol_str::SmolStr", btree_workload::); + system_memory("String", btree_workload::); +} + +/// `cargo test test_system_memory_arc --release -- --no-capture --include-ignored` +#[test] +#[rustfmt::skip] +#[ignore] +fn test_system_memory_arc() { + print_table_header("Type"); + system_memory("cold_string::ArcColdString", vec_workload::); + system_memory("cold_string::ArcColdString32", vec_workload::); + system_memory("arcstr::ArcStr", vec_workload::); + system_memory("Arc", vec_workload::); } diff --git a/cold-string/README.md b/cold-string/README.md index 17a9f47..73597fc 100644 --- a/cold-string/README.md +++ b/cold-string/README.md @@ -168,12 +168,20 @@ with 1,000,000 strings per subprocess. Each cell is the median of three isolated release-mode runs and includes the `Vec` element, heap allocation, and allocator rounding. RSS results will vary by operating system and allocator. -Type | 8 bytes | 128 bytes | 512 bytes -:--- | ---: | ---: | ---: -`Arc` | 47.1 | 175.4 | 560.0 -`arcstr::ArcStr` | 39.1 | 167.5 | 552.2 -`ArcColdString` | 8.0 | 167.5 | 552.1 -`ArcColdString32` | 8.0 | 151.4 | 536.8 +Type | 4..=4 | 8..=8 | 16..=16 | 32..=32 | 64..=64 +:--- | :---: | :---: | :---: | :---: | :---: | +`cold_string::ArcColdString` | 8.0 | 8.0 | 56.3 | 70.2 | 102.4 +`cold_string::ArcColdString32` | 8.0 | 8.0 | 38.2 | 54.5 | 86.3 +`arcstr::ArcStr` | 38.3 | 38.4 | 54.4 | 70.5 | 102.5 +`Arc` | 46.3 | 46.4 | 62.4 | 78.5 | 110.6 + +Type | 0..=4 | 0..=8 | 0..=16 | 0..=32 | 0..=64 +:--- | :---: | :---: | :---: | :---: | :---: | +`cold_string::ArcColdString` | 8.0 | 8.0 | 23.2 | 38.0 | 58.0 +`cold_string::ArcColdString32` | 8.0 | 8.0 | 21.7 | 35.9 | 55.5 +`arcstr::ArcStr` | 32.0 | 34.8 | 44.1 | 53.0 | 69.6 +`Arc` | 46.4 | 46.4 | 54.0 | 62.1 | 77.9 + ### Reference Counting Speed Comparison @@ -214,6 +222,13 @@ Type | 16 bytes | 128 bytes | 512 bytes `arcstr::ArcStr` | 2.52 | 2.53 | 2.52 `ArcColdString` | 2.17 | 2.18 | 2.19 +Clone and drop contention across 4 threads sharing strings in a fixed size pool: +Type | Light (1024) | Medium (16) | Heavy (1) +:--- | :---: | :---: | :---: +`Arc` | 26.55 | 54.69 | 144.81 +`arcstr::ArcStr` | 29.29 | 119.39 | 214.31 +`cold_string::ArcColdString32`| 25.69 | 90.53 | 262.83 + ## License Licensed under either of From 70c33396aa9b1d01d9cc399f193b30e3d3d456b2 Mon Sep 17 00:00:00 2001 From: Thomas Pendock Date: Sat, 3 Oct 2026 10:02:20 -0700 Subject: [PATCH 2/2] add len bench --- bench/Cargo.toml | 4 ++++ bench/benches/eq.rs | 19 +++++++++++++++---- bench/benches/len.rs | 29 +++++++++++++++++++++++++++++ bench/src/lib.rs | 2 +- 4 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 bench/benches/len.rs diff --git a/bench/Cargo.toml b/bench/Cargo.toml index 0ea0905..94a4d17 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -28,6 +28,10 @@ harness = false name = "eq" harness = false +[[bench]] +name = "len" +harness = false + [[bench]] name = "collection" harness = false diff --git a/bench/benches/eq.rs b/bench/benches/eq.rs index 87d54a4..c6e0f30 100644 --- a/bench/benches/eq.rs +++ b/bench/benches/eq.rs @@ -1,5 +1,7 @@ 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; @@ -10,7 +12,8 @@ const RATIOS: &[f64] = &[0.0, 0.5, 1.0]; fn build_pairs(len: usize, eq_ratio: f64) -> (Vec, Vec) where - T: From, + T: FromStr, + ::Err: Debug, { let mut left_strings = Vec::with_capacity(COUNT); let mut right_strings = Vec::with_capacity(COUNT); @@ -27,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(c: &mut Criterion, name: &str) where - T: From + PartialEq, + T: FromStr + PartialEq, + ::Err: Debug, { let mut group = c.benchmark_group(name); @@ -62,6 +72,7 @@ where fn bench_eq(c: &mut Criterion) { bench_eq_type::(c, "ColdString_eq"); + bench_eq_type::(c, "CompactString_eq"); bench_eq_type::(c, "String_eq"); } diff --git a/bench/benches/len.rs b/bench/benches/len.rs new file mode 100644 index 0000000..e15928e --- /dev/null +++ b/bench/benches/len.rs @@ -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::(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); diff --git a/bench/src/lib.rs b/bench/src/lib.rs index f33b9f2..73e2d2d 100644 --- a/bench/src/lib.rs +++ b/bench/src/lib.rs @@ -15,7 +15,7 @@ impl FromStr for StdArcStr { pub fn random_string(min: usize, max: usize) -> T { let len = fastrand::usize(min..=max); - let mut scratch = [0u8; 128]; + let mut scratch = [0u8; 255]; for byte in scratch.iter_mut().take(len) { *byte = fastrand::alphanumeric() as u8; }