Skip to content
Open
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ signals a backwards-compatible change.

## [Unreleased]

### Changed

- **Count Sketch estimates no longer allocate.** The per-row estimates a
median is taken over are collected on the stack instead of in a fresh
`Vec`: up to 20 rows for `Vector2D` (the fast and regular paths),
`BitMatrix`, `CountSketchWithL2Norm` (estimates and `l2_squared`) and
`CountSketchFold`, and a `[f64; ROWS]` array for the fixed-size matrices.
About 8 to 14 ns less per estimate on small shapes. Results are unchanged.

## [0.4.0] - 2026-10-20

Breaking release. ASAPv1 is the one serialized form: protobuf, the legacy
Expand Down
2 changes: 1 addition & 1 deletion src/common/structures/bit_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ impl MatrixStorage for BitMatrix {
Hash: MatrixFastHash,
F: Fn(&Self::Counter, usize, &Hash) -> f64,
{
let mut values: Vec<f64> = (0..self.rows)
let mut values: crate::common::structures::matrix_storage::RowEstimates = (0..self.rows)
.map(|row| {
let col = self.col_for_row(hashed_val, row);
op(&self.get(row, col), row, hashed_val)
Expand Down
6 changes: 3 additions & 3 deletions src/common/structures/fixed_structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,11 @@ macro_rules! impl_fixed_matrix {
Hash: $crate::MatrixFastHash,
F: Fn(&Self::Counter, usize, &Hash) -> f64,
{
let mut estimates = Vec::with_capacity($rows);
for row in 0..$rows {
let mut estimates = [0.0_f64; $rows];
for (row, estimate) in estimates.iter_mut().enumerate() {
let col = hashed_val.col_for_row(row, $cols);
let idx = row * $cols + col;
estimates.push(op(&self.data[idx], row, hashed_val));
*estimate = op(&self.data[idx], row, hashed_val);
}
$crate::compute_median_inline_f64(&mut estimates)
}
Expand Down
4 changes: 4 additions & 0 deletions src/common/structures/matrix_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ use smallvec::SmallVec;

use crate::DataInput;

/// Per-row estimates collected for a median, held inline up to
/// [`MATRIX_MAX_ROWS`](crate::common::MATRIX_MAX_ROWS) rows.
pub(crate) type RowEstimates = SmallVec<[f64; crate::common::MATRIX_MAX_ROWS]>;

/// Fast-path hash container for matrix-backed sketches.
#[derive(Clone, Debug)]
pub enum MatrixHash {
Expand Down
5 changes: 3 additions & 2 deletions src/common/structures/vector2d.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::common::structures::matrix_storage::RowEstimates;
use std::ops::{Index, IndexMut};

use crate::{
Expand Down Expand Up @@ -394,7 +395,7 @@ impl<T> Vector2D<T> {
Hash: MatrixFastHash,
F: Fn(&T, usize, &Hash) -> f64,
{
let mut estimates = Vec::with_capacity(self.rows);
let mut estimates = RowEstimates::with_capacity(self.rows);
for row in 0..self.rows {
let col = self.col_for_row(hashed_val, row);
let idx = row * self.cols + col;
Expand Down Expand Up @@ -566,7 +567,7 @@ impl<T> Vector2D<T> {
where
F: Fn(&T, &Q, usize, &MatrixHash) -> f64,
{
let mut estimates = Vec::with_capacity(self.rows);
let mut estimates = RowEstimates::with_capacity(self.rows);
for row in 0..self.rows {
let col = self.col_for_row(hashed_val, row);
let idx = row * self.cols + col;
Expand Down
3 changes: 2 additions & 1 deletion src/sketches/count_sketch_fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//! additional approximation error — the accuracy is identical to a full-width
//! Count Sketch with W columns.

use crate::common::structures::matrix_storage::RowEstimates;
use crate::count_min_fold::FoldCell;
use crate::{DataInput, DefaultXxHasher, SketchHasher, TopKHeap, compute_median_inline_f64};
use std::marker::PhantomData;
Expand Down Expand Up @@ -198,7 +199,7 @@ impl<H: SketchHasher> CountSketchFold<H> {
/// Returns the Count Sketch frequency estimate for `key` (median of
/// sign-corrected row estimates).
pub fn estimate(&self, key: &DataInput) -> i64 {
let mut estimates = Vec::with_capacity(self.rows);
let mut estimates = RowEstimates::with_capacity(self.rows);
for r in 0..self.rows {
let (full_col, sign) = self.hash_for(r, key);
let fc = self.fold_col_of(full_col);
Expand Down
3 changes: 2 additions & 1 deletion src/sketches/countsketch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use crate::asapv1::admission_rate::admit_all;
use crate::common::hash::check_matrix_shape;
use crate::common::structure_utils::AdmittedRows;
use crate::common::structures::matrix_storage::RowEstimates;
use crate::{
DataInput, DefaultMatrixI32, DefaultMatrixI64, DefaultMatrixI128, DefaultXxHasher, FastPath,
FastPathHasher, FixedMatrix, MatrixFastHash, MatrixStorage, NitroTarget, QuickMatrixI64,
Expand Down Expand Up @@ -381,7 +382,7 @@ where
pub fn estimate(&self, value: &DataInput) -> f64 {
let rows = self.counts.rows();
let cols = self.counts.cols();
let mut estimates = Vec::with_capacity(rows);
let mut estimates = RowEstimates::with_capacity(rows);
for r in 0..rows {
let hashed = H::hash64_seeded(r, value);
let col = ((hashed & LOWER_32_MASK) as usize) % cols;
Expand Down
5 changes: 3 additions & 2 deletions src/sketches/countsketch_l2_norm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! [`CountSketchWithL2Norm`] is the per-layer sketch of `UnivMon` and
//! `UnivMonTwoTier`; its L2 norm sets their terminal-mode heavy-hitter threshold.

use crate::common::structures::matrix_storage::RowEstimates;
use std::marker::PhantomData;

use crate::common::structures::matrix_storage::cols_mask_bits;
Expand Down Expand Up @@ -229,7 +230,7 @@ impl<H: SketchHasher> CountSketchWithL2Norm<H> {

/// Returns the median estimate of the squared L2 norm across rows.
pub fn l2_squared(&self) -> f64 {
let mut values: Vec<f64> = self.row_l2_squared.as_slice()[..self.rows]
let mut values: RowEstimates = self.row_l2_squared.as_slice()[..self.rows]
.iter()
.map(|&v| v as f64)
.collect();
Expand All @@ -254,7 +255,7 @@ impl<H: SketchHasher> CountSketchWithL2Norm<H> {
pub fn estimate_with_hash(&self, hashed_val: u128) -> f64 {
let mask_bits = self.counts.mask_bits() as usize;
let mask = (1u128 << mask_bits) - 1;
let mut lst = Vec::with_capacity(self.rows);
let mut lst = RowEstimates::with_capacity(self.rows);
let mut shift_amount = 0;
let mut sign_bit_pos = 127;

Expand Down
Loading