use std::cell::Cell;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use criterion::{Criterion, black_box, criterion_group, criterion_main};
use serde_json::{Map, Value, json};
use superposition_core::{Config, MergeStrategy, eval_config};
use superposition_types::{
Cac, Condition, Context, DimensionInfo, ExtendedMap, OverrideWithKeys, Overrides,
database::models::cac::{DependencyGraph, DimensionType},
};
const NUM_DIMENSIONS: usize = 18;
const DEFAULT_NUM_CONTEXTS: usize = 468_006;
const NUM_QUERIES: usize = 100;
const DIMENSION_CARDINALITY: usize = 12;
const DEFAULT_CONFIG_KEYS: [&str; 3] = ["config.alpha", "config.beta", "config.gamma"];
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
Rng(seed)
}
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn below(&mut self, n: usize) -> usize {
(self.next_u64() % n as u64) as usize
}
}
fn root_dimension(dim: usize) -> String {
format!("d{dim}")
}
fn cohort_dimension(dim: usize) -> String {
format!("lc{dim}")
}
fn root_value(val: usize) -> String {
format!("v{val}")
}
fn cohort_value(val: usize) -> String {
format!("c{val}")
}
fn build_dimensions() -> HashMap<String, DimensionInfo> {
let mut dimensions = HashMap::with_capacity(NUM_DIMENSIONS * 2);
for dim in 0..NUM_DIMENSIONS {
let root = root_dimension(dim);
let cohort = cohort_dimension(dim);
dimensions.insert(
root.clone(),
DimensionInfo {
schema: ExtendedMap::from(Map::new()),
position: dim as i32,
dimension_type: DimensionType::Regular {},
dependency_graph: DependencyGraph(HashMap::from([
(root.clone(), vec![cohort.clone()]),
(cohort.clone(), Vec::new()),
])),
value_compute_function_name: None,
description: String::new(),
},
);
let mut definitions = Map::new();
let mut enum_values = Vec::with_capacity(DIMENSION_CARDINALITY + 1);
for val in 0..DIMENSION_CARDINALITY {
let cohort_val = cohort_value(val);
enum_values.push(Value::String(cohort_val.clone()));
definitions.insert(
cohort_val,
json!({
"==": [
{ "var": root },
root_value(val)
]
}),
);
}
enum_values.push(Value::String("otherwise".to_string()));
let mut schema = Map::new();
schema.insert("type".to_string(), json!("string"));
schema.insert("enum".to_string(), Value::Array(enum_values));
schema.insert("definitions".to_string(), Value::Object(definitions));
dimensions.insert(
cohort.clone(),
DimensionInfo {
schema: ExtendedMap::from(schema),
position: (NUM_DIMENSIONS + dim) as i32,
dimension_type: DimensionType::LocalCohort(root),
dependency_graph: DependencyGraph(HashMap::from([(cohort, Vec::new())])),
value_compute_function_name: None,
description: String::new(),
},
);
}
dimensions
}
fn query_from_condition(condition: &Condition) -> Map<String, Value> {
condition
.iter()
.map(|(key, value)| {
if let Some(dim) = key.strip_prefix("lc") {
let root_key = format!("d{dim}");
let root_val = value
.as_str()
.and_then(|v| v.strip_prefix('c'))
.map(|v| Value::String(format!("v{v}")))
.unwrap_or_else(|| value.clone());
(root_key, root_val)
} else {
(key.clone(), value.clone())
}
})
.collect()
}
struct Dataset {
default_config: Map<String, Value>,
contexts: Vec<Context>,
overrides: HashMap<String, Overrides>,
dimensions: HashMap<String, DimensionInfo>,
queries: Vec<Map<String, Value>>,
}
fn build_dataset(num_contexts: usize) -> Dataset {
let mut rng = Rng::new(0x9E37_79B9_7F4A_7C15);
let default_config: Map<String, Value> = DEFAULT_CONFIG_KEYS
.iter()
.map(|k| (k.to_string(), json!("default")))
.collect();
let mut contexts = Vec::with_capacity(num_contexts);
let mut overrides = HashMap::with_capacity(num_contexts);
for i in 0..num_contexts {
let num_dims = 1 + rng.below(3);
let start = rng.below(NUM_DIMENSIONS);
let mut cond = Map::new();
for j in 0..num_dims {
let dim = (start + j) % NUM_DIMENSIONS;
let val = rng.below(DIMENSION_CARDINALITY);
let (key, value) = if (i + j) % 3 == 0 {
(cohort_dimension(dim), cohort_value(val))
} else {
(root_dimension(dim), root_value(val))
};
cond.insert(key, json!(value));
}
let condition = Cac::<Condition>::try_from(cond)
.expect("non-empty condition")
.into_inner();
let override_key = format!("o{i}");
let target = DEFAULT_CONFIG_KEYS[i % DEFAULT_CONFIG_KEYS.len()];
let mut override_map = Map::new();
override_map.insert(target.to_string(), json!(format!("override_{i}")));
let override_value = Cac::<Overrides>::try_from(override_map)
.expect("non-empty override")
.into_inner();
overrides.insert(override_key.clone(), override_value);
contexts.push(Context {
id: format!("ctx_{i}"),
condition,
priority: (i % 100) as i32,
weight: 1,
override_with_keys: OverrideWithKeys::new(override_key),
});
}
let mut queries = Vec::with_capacity(NUM_QUERIES);
for _ in 0..NUM_QUERIES {
let idx = rng.below(num_contexts);
queries.push(query_from_condition(&contexts[idx].condition));
}
Dataset {
default_config,
contexts,
overrides,
dimensions: build_dimensions(),
queries,
}
}
fn resolve_optimized(ds: &Dataset, query: &Map<String, Value>) -> Map<String, Value> {
eval_config(
ds.default_config.clone(),
&ds.contexts,
&ds.overrides,
&ds.dimensions,
query,
MergeStrategy::MERGE,
None,
)
.expect("resolve")
}
fn resolve_pre_optimization(
ds: &Dataset,
query: &Map<String, Value>,
) -> Map<String, Value> {
let full = Config {
contexts: ds.contexts.clone(),
overrides: ds.overrides.clone(),
default_configs: ds.default_config.clone().into(),
dimensions: ds.dimensions.clone(),
};
let _dimensions = full.dimensions.clone();
let contexts = ds.contexts.clone();
let overrides = ds.overrides.clone();
eval_config(
ds.default_config.clone(),
&contexts,
&overrides,
&ds.dimensions,
query,
MergeStrategy::MERGE,
None,
)
.expect("resolve")
}
fn bench_resolve(c: &mut Criterion) {
let num_contexts = std::env::var("BENCH_CONTEXTS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_NUM_CONTEXTS);
let start = Instant::now();
let ds = build_dataset(num_contexts);
eprintln!(
"Built dataset: {} contexts, {} regular dimensions, {} local cohorts, {} queries in {:.2?}",
num_contexts,
NUM_DIMENSIONS,
NUM_DIMENSIONS,
ds.queries.len(),
start.elapsed()
);
let mut group = c.benchmark_group("resolve_all_config");
group.sample_size(10);
group.warm_up_time(Duration::from_secs(1));
group.measurement_time(Duration::from_secs(15));
group.bench_function("optimized_borrowed", |b| {
let counter = Cell::new(0usize);
b.iter(|| {
let q = &ds.queries[counter.get() % ds.queries.len()];
counter.set(counter.get() + 1);
black_box(resolve_optimized(&ds, q))
})
});
group.bench_function("pre_optimization_cloned", |b| {
let counter = Cell::new(0usize);
b.iter(|| {
let q = &ds.queries[counter.get() % ds.queries.len()];
counter.set(counter.get() + 1);
black_box(resolve_pre_optimization(&ds, q))
})
});
group.finish();
}
criterion_group!(benches, bench_resolve);
criterion_main!(benches);