use std::alloc::{GlobalAlloc, Layout, System};
use std::collections::VecDeque;
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use std::time::Instant;
use timely::Container;
use timely::container::{PushInto, SizableContainer};
use differential_dataflow::consolidation::Consolidate;
use differential_dataflow::columnar::layout::ColumnarUpdate;
use differential_dataflow::trace::chunk::{merge_chains, Chunk, NavigableChunk};
use differential_dataflow::trace::chunk::vec::VecChunk;
use differential_dataflow::columnar::trace::ColChunk;
use differential_dataflow::trace::cursor::Cursor;
use differential_dataflow::trace::implementations::chunker::ContainerChunker;
struct Counting;
static RESIDENT: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, l: Layout) -> *mut u8 { RESIDENT.fetch_add(l.size(), Relaxed); System.alloc(l) }
unsafe fn dealloc(&self, p: *mut u8, l: Layout) { RESIDENT.fetch_sub(l.size(), Relaxed); System.dealloc(p, l) }
unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 {
if new >= l.size() { RESIDENT.fetch_add(new - l.size(), Relaxed); } else { RESIDENT.fetch_sub(l.size() - new, Relaxed); }
System.realloc(p, l, new)
}
}
#[global_allocator]
static ALLOC: Counting = Counting;
fn resident() -> usize { RESIDENT.load(Relaxed) }
fn build_chain<C, I>(mut updates: Vec<I>) -> Vec<C>
where
C: Default + SizableContainer + Consolidate + Container + PushInto<I>,
{
use timely::container::ContainerBuilder;
let mut chunker = ContainerChunker::<C>::default();
chunker.push_into(&mut updates);
let mut out = Vec::new();
while let Some(c) = chunker.finish() { out.push(std::mem::take(c)); }
out
}
fn scan<C: NavigableChunk>(chunks: &[C]) -> usize {
let mut count = 0;
for c in chunks {
let mut cur = c.cursor();
while cur.key_valid(c) {
while cur.val_valid(c) {
cur.map_times(c, |_t, _d| count += 1);
cur.step_val(c);
}
cur.step_key(c);
}
}
count
}
fn ms(t: Instant) -> f64 { t.elapsed().as_secs_f64() * 1000.0 }
fn bench<C, I>(updates: &[I], half_a: &[I], half_b: &[I]) -> (f64, f64, f64, f64, usize, usize)
where
C: NavigableChunk + Default + SizableContainer + Consolidate + Container + PushInto<I>,
I: Clone,
{
let (half_a, half_b) = (half_a.to_vec(), half_b.to_vec());
let base = resident();
let t = Instant::now();
let chain = build_chain::<C, I>(updates.to_vec());
let build_ms = ms(t);
let bytes = resident().saturating_sub(base);
let records: usize = chain.iter().map(<C as Chunk>::len).sum();
let chain_a = build_chain::<C, I>(half_a);
let chain_b = build_chain::<C, I>(half_b);
let t = Instant::now();
let mut merged = VecDeque::new();
merge_chains::<C>(chain_a, chain_b, &mut merged);
let merge_ms = ms(t);
let merged: Vec<C> = merged.into();
let t = Instant::now();
let scanned = std::hint::black_box(scan(&merged));
let scan_ms = ms(t);
assert!(scanned <= records.max(1) * 2);
drop((chain, merged));
(build_ms, merge_ms, scan_ms, bytes as f64, records, bytes)
}
fn alt<I: Clone>(updates: &[I]) -> (Vec<I>, Vec<I>) {
(updates.iter().step_by(2).cloned().collect(), updates.iter().skip(1).step_by(2).cloned().collect())
}
fn compare<KV, I>(label: &str, updates: Vec<I>)
where
I: Clone,
VecChunk<KV, (), u64, i64>: Chunk + Default + SizableContainer + Consolidate + Container + PushInto<I>,
ColChunk<(KV, (), u64, i64)>: Chunk + Default + SizableContainer + Consolidate + Container + PushInto<I>,
(KV, (), u64, i64): ColumnarUpdate<Key = KV, Val = (), Time = u64, Diff = i64>,
KV: Ord + Clone + 'static,
{
let (a, b) = alt(&updates);
let (vb, vm, vs, _vbytes, recs, vbytes) = bench::<VecChunk<KV, (), u64, i64>, I>(&updates, &a, &b);
let (cb, cm, cs, _cbytes, _recs, cbytes) = bench::<ColChunk<(KV, (), u64, i64)>, I>(&updates, &a, &b);
println!("\n== {label} ({recs} records) ==");
println!(" {:>12} {:>12} {:>10}", "vec(flat)", "col(trie)", "col/vec");
println!(" build ms {:>12.1} {:>12.1} {:>9.2}x", vb, cb, cb / vb);
println!(" merge ms {:>12.1} {:>12.1} {:>9.2}x", vm, cm, cm / vm);
println!(" scan ms {:>12.1} {:>12.1} {:>9.2}x", vs, cs, cs / vs);
println!(" bytes {:>12} {:>12} {:>9.2}x", vbytes, cbytes, cbytes as f64 / vbytes as f64);
println!(" bytes/rec {:>12.1} {:>12.1}", vbytes as f64 / recs as f64, cbytes as f64 / recs as f64);
}
fn main() {
let n: usize = std::env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(1_000_000);
let mut seed = 0x2545F4914F6CDD1Du64;
let mut rng = move || { seed ^= seed << 13; seed ^= seed >> 7; seed ^= seed << 17; seed };
{
let mut u: Vec<((u64, ()), u64, i64)> = (0..n as u64).map(|k| ((k, ()), 0u64, 1i64)).collect();
for i in 0..u.len() { let j = (rng() as usize) % u.len(); u.swap(i, j); }
compare::<u64, _>("A: unique u64 keys, () vals", u);
}
{
let keys = (n as f64).sqrt() as u64;
let mut u: Vec<((u64, u64), u64, i64)> =
(0..n as u64).map(|_| { let k = rng() % keys; let v = rng() % keys; ((k, v), 0u64, 1i64) }).collect();
u.sort();
let (a, b) = alt(&u);
bench_valbearing("B: few u64 keys, many random vals (interleaved merge)", u, a, b);
}
{
let cardinality = (n / 20).max(1) as u64; let mut u: Vec<((String, ()), u64, i64)> =
(0..n as u64).map(|_| { let k = rng() % cardinality; ((format!("key-{k:012}"), ()), 0u64, 1i64) }).collect();
u.sort();
compare::<String, _>("C: String keys (~20x repeats), () vals", u);
}
{
let pairs = ((n as f64).sqrt() as u64).max(1);
let half = n as u64 / 2;
let mut u: Vec<((u64, u64), u64, i64)> =
(0..n as u64).map(|i| { let p = i % pairs; let t = i / pairs; ((p, 0u64), t, 1i64) }).collect();
u.sort();
let a: Vec<_> = u.iter().filter(|(_, t, _)| *t < half / pairs).cloned().collect();
let b: Vec<_> = u.iter().filter(|(_, t, _)| *t >= half / pairs).cloned().collect();
bench_valbearing("D: few (k,v), many times, time-disjoint merge (LSM append)", u, a, b);
}
}
fn bench_valbearing(label: &str, updates: Vec<((u64, u64), u64, i64)>, half_a: Vec<((u64, u64), u64, i64)>, half_b: Vec<((u64, u64), u64, i64)>) {
let (vb, vm, vs, _v, recs, vbytes) = bench::<VecChunk<u64, u64, u64, i64>, _>(&updates, &half_a, &half_b);
let (cb, cm, cs, _c, _r, cbytes) = bench::<ColChunk<(u64, u64, u64, i64)>, _>(&updates, &half_a, &half_b);
println!("\n== {label} ({recs} records) ==");
println!(" {:>12} {:>12} {:>10}", "vec(flat)", "col(trie)", "col/vec");
println!(" build ms {:>12.1} {:>12.1} {:>9.2}x", vb, cb, cb / vb);
println!(" merge ms {:>12.1} {:>12.1} {:>9.2}x", vm, cm, cm / vm);
println!(" scan ms {:>12.1} {:>12.1} {:>9.2}x", vs, cs, cs / vs);
println!(" bytes {:>12} {:>12} {:>9.2}x", vbytes, cbytes, cbytes as f64 / vbytes as f64);
println!(" bytes/rec {:>12.1} {:>12.1}", vbytes as f64 / recs as f64, cbytes as f64 / recs as f64);
}