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
2 changes: 1 addition & 1 deletion aqpbm-cli/src/rows/statistic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub(super) fn cardinality_row<T: ColumnItem>(
}

/// A row answering **top-k**: the heaviest `k` keys, ranked. The probe is
/// `()` — one question, the whole list, asked once — and `k` is
/// `()` — one question, the whole list, asked repeatedly for timing — and `k` is
/// `chl::CMS_HEAP_TOP_K`, the same compile-time constant the sketch was built
/// with, so the heap's capacity and the truth it's graded against can never
/// silently disagree (see #95's design-decision comment on the registry entry).
Expand Down
12 changes: 10 additions & 2 deletions aqpbm-core/src/accuracy/topk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ use aqpbm_datagen::{DataGenError, GeneratedTable};

use super::{CountedValue, GroundTruth};

/// How many times to put the one question. A heap dump of `k` items takes
/// about a microsecond, so with one call per pass the CPU time read from
/// `getrusage` around it is mostly the cost of the reading itself. As for
/// `CardinalityGT`, the count is this statistic's knowledge, not the runner's.
const QUERY_TIMING_REPEATS: usize = 1024;

pub struct TopkGT<K> {
pub k: usize,
pub column: usize,
Expand All @@ -31,7 +37,7 @@ where
{
/// The true k heaviest keys, in the total order every top-k impl ranks by.
type Truth = Vec<(K, u64)>;
/// One question: the whole list. A probe carries nothing.
/// One question, the whole list, asked repeatedly. A probe carries nothing.
type Probe = ();
type Answer = Vec<(K, u64)>;

Expand Down Expand Up @@ -60,7 +66,7 @@ where
}

fn probes(&self, _truth: &Vec<(K, u64)>) -> Vec<()> {
vec![()]
vec![(); QUERY_TIMING_REPEATS]
}

fn score(
Expand All @@ -69,6 +75,8 @@ where
_probes: &[()],
answers: &[Vec<(K, u64)>],
) -> BTreeMap<String, f64> {
// Every answer is to the same question, so the first is the estimate
// and the rest existed to make the timing readable.
let empty = Vec::new();
let est = answers.first().unwrap_or(&empty);
let est_set: std::collections::HashSet<K::CountKey> =
Expand Down
2 changes: 1 addition & 1 deletion rqe-optimizer/src/analytical_cost_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub const OUTPUT_BYTES_PER_TOPK_ENTRY: f64 = 16.0;
/// Top-k entries per group: the heap size every top-k cost row was measured
/// at (sketch-bench's `CMS_HEAP_TOP_K`).
// ponytail: fixed k. A large k needs `k` on the RAQE and a benchmark that
// varies the heap, whose memory the export leaves out.
// varies the heap.
pub const TOPK_ENTRIES: f64 = 32.0;

/// One phase's resource use.
Expand Down
12 changes: 6 additions & 6 deletions sketch-bench/src/wrappers/cms_heap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,24 @@ mod tests {
}
}

/// Footprint is `rows * cols` counters at the real width — the heap itself
/// isn't counted, matching how the plain CMS rows don't count their own
/// bookkeeping fields either.
/// Footprint is `rows * cols` counters at the real width plus a full heap
/// of `CMS_HEAP_TOP_K` `HHItem`s, as UnivMon counts its heaps (#146).
#[test]
fn footprint_is_rows_times_cols_times_counter_width() {
fn footprint_is_counters_plus_heap() {
let p = ParamSet::of(&CmsHeapParams {
rows: 5,
cols: 2048,
});
let heap = CMS_HEAP_TOP_K * std::mem::size_of::<asap_sketchlib::input::HHItem>();
let fast = build_cms_heap_lib_vector2d_fast(&p).expect("5x2048 is a valid shape");
assert_eq!(
memory_cms_heap_lib_vector2d_fast(&fast),
5 * 2048 * std::mem::size_of::<i32>()
5 * 2048 * std::mem::size_of::<i32>() + heap
);
let regular = build_cms_heap_lib_vector2d_regular(&p).expect("5x2048 is a valid shape");
assert_eq!(
memory_cms_heap_lib_vector2d_regular(&regular),
5 * 2048 * std::mem::size_of::<i32>()
5 * 2048 * std::mem::size_of::<i32>() + heap
);
}
}
82 changes: 47 additions & 35 deletions sketch-bench/src/wrappers/cms_heap/sketchlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::wrappers::{require_positive, BuildError, Pass, QueryPass, Shared, Ste
use std::cell::RefCell;
use std::rc::Rc;

use asap_sketchlib::input::HHItem;
use asap_sketchlib::{heap_item_to_sketch_input, CMSHeap, FastPath, RegularPath, Vector2D};

/// The heap's fixed capacity, and the `k` every TopK-capability row grades
Expand Down Expand Up @@ -47,11 +48,11 @@ pub fn build_cms_heap_lib_vector2d_fast(
})
}

/// The counter matrix plus a full heap of `CMS_HEAP_TOP_K` `HHItem`s, counted
/// the way UnivMon counts its heaps so the TopK rows compare like for like.
pub fn memory_cms_heap_lib_vector2d_fast(sketch: &CmsHeapLibVector2dFast) -> usize {
// The heap holds at most `CMS_HEAP_TOP_K` items — negligible next to the
// counter matrix, and not counted here, matching how the plain CMS rows
// don't count their own bookkeeping fields either.
sketch.rows * sketch.cols * std::mem::size_of::<i32>()
+ CMS_HEAP_TOP_K * std::mem::size_of::<HHItem>()
}

// ---------- asap_sketchlib: Vector2D + RegularPath ----------
Expand All @@ -74,8 +75,10 @@ pub fn build_cms_heap_lib_vector2d_regular(
})
}

/// As [`memory_cms_heap_lib_vector2d_fast`].
pub fn memory_cms_heap_lib_vector2d_regular(sketch: &CmsHeapLibVector2dRegular) -> usize {
sketch.rows * sketch.cols * std::mem::size_of::<i32>()
+ CMS_HEAP_TOP_K * std::mem::size_of::<HHItem>()
}

// ---------- insert / insert_step (FastPath) ----------
Expand Down Expand Up @@ -246,10 +249,9 @@ pub fn merge_query_cms_heap_lib_vector2d_regular_estimate<T: FrequencyValue>(

// ---------- query: heap dump (TopK capability) ----------
//
// One `()` probe, one answer: the whole ranked heap, read off in one call.
// `TopkGT`'s `probes()` hands back `vec![()]`, so `probes` here is always
// length 1 — there is nothing per-item to loop over the way the estimate
// queries do.
// One answer per `()` probe: the whole ranked heap, read off in one call.
// Every probe is the same question; `TopkGT` repeats it so a pass's timed
// region sits well above the timer floor, as `CardinalityGT` does for HLL.

pub fn query_cms_heap_lib_vector2d_fast_topk<T: FrequencyValue>(
params: &ParamSet,
Expand All @@ -265,7 +267,7 @@ pub fn query_cms_heap_lib_vector2d_fast_topk<T: FrequencyValue>(
pub fn merge_query_cms_heap_lib_vector2d_fast_topk<T: FrequencyValue>(
params: &ParamSet,
items: Rc<Vec<T>>,
_probes: Rc<Vec<()>>,
probes: Rc<Vec<()>>,
shards: usize,
passes: usize,
) -> Result<Vec<QueryPass<TopkAnswer<T>>>, BuildError> {
Expand All @@ -276,21 +278,26 @@ pub fn merge_query_cms_heap_lib_vector2d_fast_topk<T: FrequencyValue>(
for other in rest.iter() {
sketch.inner.merge(&other.inner);
}
let probes = probes.clone();
out.push(Box::new(move || {
let ranked: TopkAnswer<T> = sketch
.inner
.heap()
.heap()
.iter()
.map(|item| {
(
T::from_data_input(&heap_item_to_sketch_input(&item.key)),
item.count as u64,
)
})
.collect();
let mut answers = Vec::with_capacity(probes.len());
for _p in probes.iter() {
let ranked: TopkAnswer<T> = sketch
.inner
.heap()
.heap()
.iter()
.map(|item| {
(
T::from_data_input(&heap_item_to_sketch_input(&item.key)),
item.count as u64,
)
})
.collect();
answers.push(ranked);
}
let footprint = memory_cms_heap_lib_vector2d_fast(&sketch);
(vec![ranked], footprint)
(answers, footprint)
}) as QueryPass<TopkAnswer<T>>);
}
Ok(out)
Expand All @@ -310,7 +317,7 @@ pub fn query_cms_heap_lib_vector2d_regular_topk<T: FrequencyValue>(
pub fn merge_query_cms_heap_lib_vector2d_regular_topk<T: FrequencyValue>(
params: &ParamSet,
items: Rc<Vec<T>>,
_probes: Rc<Vec<()>>,
probes: Rc<Vec<()>>,
shards: usize,
passes: usize,
) -> Result<Vec<QueryPass<TopkAnswer<T>>>, BuildError> {
Expand All @@ -321,21 +328,26 @@ pub fn merge_query_cms_heap_lib_vector2d_regular_topk<T: FrequencyValue>(
for other in rest.iter() {
sketch.inner.merge(&other.inner);
}
let probes = probes.clone();
out.push(Box::new(move || {
let ranked: TopkAnswer<T> = sketch
.inner
.heap()
.heap()
.iter()
.map(|item| {
(
T::from_data_input(&heap_item_to_sketch_input(&item.key)),
item.count as u64,
)
})
.collect();
let mut answers = Vec::with_capacity(probes.len());
for _p in probes.iter() {
let ranked: TopkAnswer<T> = sketch
.inner
.heap()
.heap()
.iter()
.map(|item| {
(
T::from_data_input(&heap_item_to_sketch_input(&item.key)),
item.count as u64,
)
})
.collect();
answers.push(ranked);
}
let footprint = memory_cms_heap_lib_vector2d_regular(&sketch);
(vec![ranked], footprint)
(answers, footprint)
}) as QueryPass<TopkAnswer<T>>);
}
Ok(out)
Expand Down
37 changes: 22 additions & 15 deletions sketch-bench/src/wrappers/cs_heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::params::{CountSketchParams, ParamSet};
use crate::wrappers::cms_heap::sketchlib::{TopkAnswer, CMS_HEAP_TOP_K};
use crate::wrappers::frequency_value::FrequencyValue;
use crate::wrappers::{require_positive, BuildError, Pass, QueryPass, Shared, StepPass};
use asap_sketchlib::input::HHItem;
use asap_sketchlib::{heap_item_to_sketch_input, CSHeap, FastPath, Vector2D};
use std::cell::RefCell;
use std::rc::Rc;
Expand All @@ -31,9 +32,10 @@ pub fn build_cs_heap_lib_vector2d_fast(
})
}

/// Counter matrix only, as `cms_heap` counts it.
/// Counter matrix plus a full heap, as `cms_heap` counts it.
pub fn memory_cs_heap_lib_vector2d_fast(sketch: &CsHeapLibVector2dFast) -> usize {
sketch.rows * sketch.cols * std::mem::size_of::<i32>()
+ CMS_HEAP_TOP_K * std::mem::size_of::<HHItem>()
}

pub fn insert_cs_heap_lib_vector2d_fast<T: FrequencyValue>(
Expand Down Expand Up @@ -86,7 +88,7 @@ pub fn query_cs_heap_lib_vector2d_fast_topk<T: FrequencyValue>(
pub fn merge_query_cs_heap_lib_vector2d_fast_topk<T: FrequencyValue>(
params: &ParamSet,
items: Rc<Vec<T>>,
_probes: Rc<Vec<()>>,
probes: Rc<Vec<()>>,
shards: usize,
passes: usize,
) -> Result<Vec<QueryPass<TopkAnswer<T>>>, BuildError> {
Expand All @@ -96,21 +98,26 @@ pub fn merge_query_cs_heap_lib_vector2d_fast_topk<T: FrequencyValue>(
for other in rest.iter() {
sketch.inner.merge(&other.inner);
}
let probes = probes.clone();
out.push(Box::new(move || {
let ranked: TopkAnswer<T> = sketch
.inner
.heap()
.heap()
.iter()
.map(|item| {
(
T::from_data_input(&heap_item_to_sketch_input(&item.key)),
item.count.max(0) as u64,
)
})
.collect();
let mut answers = Vec::with_capacity(probes.len());
for _p in probes.iter() {
let ranked: TopkAnswer<T> = sketch
.inner
.heap()
.heap()
.iter()
.map(|item| {
(
T::from_data_input(&heap_item_to_sketch_input(&item.key)),
item.count.max(0) as u64,
)
})
.collect();
answers.push(ranked);
}
let footprint = memory_cs_heap_lib_vector2d_fast(&sketch);
(vec![ranked], footprint)
(answers, footprint)
}) as QueryPass<TopkAnswer<T>>);
}
Ok(out)
Expand Down
25 changes: 16 additions & 9 deletions sketch-bench/src/wrappers/exact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,20 +234,27 @@ impl<C: Cell> Multiple<C> {
.ok_or_else(|| AggregateQueryError::GroupNotFound(group.to_vec()))
}

/// Key and cell bytes per group; hash-table overhead left out.
/// The hash table plus what each group owns outside it. The table is an
/// estimate of hashbrown's layout: one `(key, cell)` slot and one control
/// byte per unit of capacity. It ignores the few trailing control bytes
/// and the gap between `capacity()` and the bucket count. Capacity is what
/// is allocated, so the per-group share depends on the load factor at the
/// measured group count (~1.44 slots per group at the export's ~9.9k
/// groups); other group counts land between 1 and 2.
fn footprint(&self) -> usize {
self.by_group
let table = self.by_group.capacity()
* (std::mem::size_of::<Vec<String>>() + std::mem::size_of::<C>() + 1);
let owned: usize = self
.by_group
.iter()
.map(|(key, cell)| {
std::mem::size_of::<Vec<String>>()
+ key
.iter()
.map(|l| std::mem::size_of::<String>() + l.len())
.sum::<usize>()
+ std::mem::size_of::<C>()
key.iter()
.map(|l| std::mem::size_of::<String>() + l.len())
.sum::<usize>()
+ cell.heap_bytes()
})
.sum()
.sum();
table + owned
}
}

Expand Down
33 changes: 19 additions & 14 deletions sketch-bench/src/wrappers/univmon/sketchlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ pub fn query_univmon_lib_topk<T: FrequencyValue>(
pub fn merge_query_univmon_lib_topk<T: FrequencyValue>(
params: &ParamSet,
items: Rc<Vec<T>>,
_probes: Rc<Vec<()>>,
probes: Rc<Vec<()>>,
shards: usize,
passes: usize,
) -> Result<Vec<QueryPass<TopkAnswer<T>>>, BuildError> {
Expand All @@ -411,21 +411,26 @@ pub fn merge_query_univmon_lib_topk<T: FrequencyValue>(
for other in rest.iter() {
sketch.inner.merge(&other.inner);
}
let probes = probes.clone();
out.push(Box::new(move || {
let mut heap: Vec<_> = sketch.inner.hh_layers[0].heap().iter().collect();
heap.sort_unstable_by_key(|item| std::cmp::Reverse(item.count));
let ranked: TopkAnswer<T> = heap
.into_iter()
.take(CMS_HEAP_TOP_K)
.map(|item| {
(
T::from_data_input(&heap_item_to_sketch_input(&item.key)),
item.count.max(0) as u64,
)
})
.collect();
let mut answers = Vec::with_capacity(probes.len());
for _p in probes.iter() {
let mut heap: Vec<_> = sketch.inner.hh_layers[0].heap().iter().collect();
heap.sort_unstable_by_key(|item| std::cmp::Reverse(item.count));
let ranked: TopkAnswer<T> = heap
.into_iter()
.take(CMS_HEAP_TOP_K)
.map(|item| {
(
T::from_data_input(&heap_item_to_sketch_input(&item.key)),
item.count.max(0) as u64,
)
})
.collect();
answers.push(ranked);
}
let footprint = memory_univmon_lib(&sketch);
(vec![ranked], footprint)
(answers, footprint)
}) as QueryPass<TopkAnswer<T>>);
}
Ok(out)
Expand Down
Loading