#![allow(dead_code)]
use std::sync::Arc;
use znippy_zoomies::stree::STree64;
pub struct RagnarLog<V> {
keys: Arc<[i64]>,
entries: Arc<[V]>,
exact: Option<STree64>,
eytz_keys: Vec<i64>,
eytz_idx: Vec<u32>,
min_key: i64,
max_key: i64,
}
impl<V> std::fmt::Debug for RagnarLog<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RagnarLog")
.field("len", &self.entries.len())
.field("min_key", &self.min_key)
.field("max_key", &self.max_key)
.finish()
}
}
impl<V> RagnarLog<V> {
pub fn build(rows: Vec<(i64, V)>) -> Self {
let mut tagged: Vec<(i64, usize, V)> = rows
.into_iter()
.enumerate()
.map(|(i, (k, v))| (k, i, v))
.collect();
tagged.sort_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
tagged.dedup_by_key(|t| t.0);
let keys: Vec<i64> = tagged.iter().map(|t| t.0).collect();
let entries: Vec<V> = tagged.into_iter().map(|t| t.2).collect();
if keys.is_empty() {
return Self {
keys: Arc::from(Vec::new()),
entries: Arc::from(Vec::new()),
exact: None,
eytz_keys: vec![i64::MIN], eytz_idx: vec![0],
min_key: 0,
max_key: 0,
};
}
let min_key = keys[0];
let max_key = *keys.last().unwrap();
let exact = Some(STree64::new(&keys));
let (eytz_keys, eytz_idx) = build_eytzinger(&keys);
Self {
keys: keys.into(),
entries: entries.into(),
exact,
eytz_keys,
eytz_idx,
min_key,
max_key,
}
}
#[inline]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[inline]
pub fn min_key(&self) -> i64 {
self.min_key
}
#[inline]
pub fn max_key(&self) -> i64 {
self.max_key
}
#[inline]
pub fn at_exact(&self, key: i64) -> Option<&V> {
let idx = self.exact.as_ref()?.find_exact(key)?;
debug_assert_eq!(self.keys.get(idx).copied(), Some(key));
self.entries.get(idx)
}
#[inline]
pub fn as_of(&self, key: i64) -> Option<&V> {
let sorted = self.floor_index(key)?;
self.entries.get(sorted)
}
#[inline]
pub fn boundary_leq(&self, key: i64) -> usize {
match self.floor_index(key) {
Some(i) => i + 1,
None => 0,
}
}
#[inline]
fn floor_index(&self, q: i64) -> Option<usize> {
let n = self.keys.len();
if n == 0 || q < self.min_key {
return None;
}
let mut k = 1usize;
let mut best = u32::MAX;
while k <= n {
prefetch(&self.eytz_keys, 2 * k);
if self.eytz_keys[k] <= q {
best = self.eytz_idx[k];
k = 2 * k + 1; } else {
k = 2 * k; }
}
(best != u32::MAX).then_some(best as usize)
}
#[inline]
pub fn get(&self, i: usize) -> Option<(i64, &V)> {
Some((*self.keys.get(i)?, self.entries.get(i)?))
}
#[inline]
pub fn keys(&self) -> &[i64] {
&self.keys
}
}
fn build_eytzinger(sorted: &[i64]) -> (Vec<i64>, Vec<u32>) {
let n = sorted.len();
let mut ek = vec![i64::MIN; n + 1];
let mut ei = vec![0u32; n + 1];
let mut pos = 0usize;
fill(1, n, sorted, &mut ek, &mut ei, &mut pos);
(ek, ei)
}
fn fill(k: usize, n: usize, sorted: &[i64], ek: &mut [i64], ei: &mut [u32], pos: &mut usize) {
if k > n {
return;
}
fill(2 * k, n, sorted, ek, ei, pos);
ek[k] = sorted[*pos];
ei[k] = *pos as u32;
*pos += 1;
fill(2 * k + 1, n, sorted, ek, ei, pos);
}
#[inline(always)]
fn prefetch(eytz: &[i64], slot: usize) {
#[cfg(target_arch = "x86_64")]
{
if slot < eytz.len() {
unsafe {
use std::arch::x86_64::{_mm_prefetch, _MM_HINT_T0};
_mm_prefetch::<_MM_HINT_T0>(eytz.as_ptr().add(slot) as *const i8);
}
}
}
#[cfg(not(target_arch = "x86_64"))]
{
let _ = (eytz, slot);
}
}
pub struct LinearScan<V> {
rows: Vec<(i64, usize, V)>, }
impl<V> LinearScan<V> {
pub fn build(rows: Vec<(i64, V)>) -> Self {
let rows = rows
.into_iter()
.enumerate()
.map(|(i, (k, v))| (k, i, v))
.collect();
Self { rows }
}
pub fn at_exact(&self, key: i64) -> Option<&V> {
let mut best: Option<&(i64, usize, V)> = None;
for r in &self.rows {
if r.0 == key && best.map(|b| r.1 > b.1).unwrap_or(true) {
best = Some(r);
}
}
best.map(|r| &r.2)
}
pub fn as_of(&self, key: i64) -> Option<&V> {
let mut best: Option<&(i64, usize, V)> = None;
for r in &self.rows {
if r.0 > key {
continue;
}
let take = match best {
None => true,
Some(b) => r.0 > b.0 || (r.0 == b.0 && r.1 > b.1),
};
if take {
best = Some(r);
}
}
best.map(|r| &r.2)
}
pub fn boundary_leq(&self, key: i64) -> usize {
let mut seen: Vec<i64> = self
.rows
.iter()
.filter(|r| r.0 <= key)
.map(|r| r.0)
.collect();
seen.sort_unstable();
seen.dedup();
seen.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
fn below(&mut self, n: i64) -> i64 {
(self.next() % n as u64) as i64
}
}
#[test]
fn empty_log_answers_none() {
let idx: RagnarLog<u32> = RagnarLog::build(vec![]);
assert!(idx.is_empty());
assert_eq!(idx.at_exact(5), None);
assert_eq!(idx.as_of(5), None);
assert_eq!(idx.boundary_leq(5), 0);
}
#[test]
fn floor_and_exact_on_a_hand_log() {
let idx = RagnarLog::build(vec![(30, "c"), (10, "a"), (20, "b")]);
assert_eq!(idx.len(), 3);
assert_eq!(idx.min_key(), 10);
assert_eq!(idx.max_key(), 30);
assert_eq!(idx.at_exact(10), Some(&"a"));
assert_eq!(idx.at_exact(20), Some(&"b"));
assert_eq!(idx.at_exact(30), Some(&"c"));
assert_eq!(idx.at_exact(15), None);
assert_eq!(idx.as_of(9), None);
assert_eq!(idx.as_of(10), Some(&"a"));
assert_eq!(idx.as_of(25), Some(&"b"));
assert_eq!(idx.as_of(30), Some(&"c"));
assert_eq!(idx.as_of(99), Some(&"c"));
assert_eq!(idx.boundary_leq(9), 0);
assert_eq!(idx.boundary_leq(10), 1);
assert_eq!(idx.boundary_leq(25), 2);
assert_eq!(idx.boundary_leq(999), 3);
}
#[test]
fn last_writer_wins_on_duplicate_keys() {
let rows = vec![(10, "a"), (20, "b_old"), (30, "c"), (20, "b_new")];
let idx = RagnarLog::build(rows.clone());
assert_eq!(idx.len(), 3, "duplicate key collapses");
assert_eq!(idx.at_exact(20), Some(&"b_new"));
assert_eq!(idx.as_of(25), Some(&"b_new"));
let lin = LinearScan::build(rows);
assert_eq!(lin.at_exact(20), Some(&"b_new"));
assert_eq!(lin.as_of(25), Some(&"b_new"));
}
#[test]
fn differential_ragnar_matches_linear() {
let mut rng = Rng(0x9E37_79B9_7F4A_7C15);
let n = 50_000;
let key_space = 20_000i64;
let rows: Vec<(i64, u64)> = (0..n)
.map(|_| (rng.below(key_space), rng.next()))
.collect();
let ragnar = RagnarLog::build(rows.clone());
let linear = LinearScan::build(rows);
let mut mismatches = 0u64;
for q in -3..=(key_space + 3) {
if ragnar.at_exact(q) != linear.at_exact(q) {
mismatches += 1;
}
if ragnar.as_of(q) != linear.as_of(q) {
mismatches += 1;
}
if ragnar.boundary_leq(q) != linear.boundary_leq(q) {
mismatches += 1;
}
}
assert_eq!(mismatches, 0, "static index diverged from linear scan");
assert_eq!(ragnar.min_key(), 0.max(ragnar.min_key()));
}
#[test]
fn differential_sparse_snapshot_ids() {
let mut rng = Rng(0xDEAD_BEEF_CAFE_1234);
let n = 10_000;
let mut keys: Vec<i64> = Vec::with_capacity(n);
let mut k = 1_000_000i64;
for _ in 0..n {
k += 1 + rng.below(1_000); keys.push(k);
}
let rows: Vec<(i64, i64)> = keys.iter().map(|&k| (k, k * 2)).collect();
let ragnar = RagnarLog::build(rows.clone());
let linear = LinearScan::build(rows);
for _ in 0..50_000 {
let q = 1_000_000 + rng.below(n as i64 * 1_100);
assert_eq!(ragnar.at_exact(q), linear.at_exact(q));
assert_eq!(ragnar.as_of(q), linear.as_of(q));
assert_eq!(ragnar.boundary_leq(q), linear.boundary_leq(q));
}
for &kk in keys.iter().step_by(97) {
assert_eq!(ragnar.at_exact(kk), Some(&(kk * 2)));
}
}
}