Skip to content

Delta scheme estimation overlooks bases cost #9601

Description

@rapour

What happened?

Going through #9556 reveals another issue pertaining delta scheme estimation model. Compressor favors delta over pco in

fn list_of_int_runs() -> VortexResult<ArrayRef> {
let mut rng = StdRng::seed_from_u64(118);
let elements = int_runs();
let mut offsets: Vec<i32> = Vec::with_capacity(N / 4 + 1);
let mut offset = 0i32;
offsets.push(offset);
while (offset as usize) < N {
offset = (offset + rng.random_range(1..8)).min(N as i32);
offsets.push(offset);
}
let offsets = PrimitiveArray::new(Buffer::copy_from(&offsets), Validity::NonNullable);
Ok(ListArray::try_new(elements, offsets.into_array(), Validity::NonNullable)?.into_array())
}

and ends up storing 4748 bytes compared to 4434 bytes of pco.

---
input: list(i32), len=4066, nbytes=81804
root: vortex.list(list(i32), len=4066) nbytes=4748
  metadata: 
  elements: vortex.zigzag(i32, len=16384) nbytes=2969
    metadata: 
    encoded: vortex.pco(u32, len=16384) nbytes=2969
      metadata: ptype: u32, nrows: 16384, slice: 0..16384
  offsets: fastlanes.delta(u16, len=4067) nbytes=1779
    metadata: offset: 0
    bases: vortex.pco(u16, len=256) nbytes=243
      metadata: ptype: u16, nrows: 256, slice: 0..256
    deltas: fastlanes.bitpacked(u16, len=4096) nbytes=1536
      metadata: bit_width: 3, offset: 0
input: list(i32), len=4066, nbytes=81804
root: vortex.list(list(i32), len=4066) nbytes=4434
  metadata: 
  elements: vortex.zigzag(i32, len=16384) nbytes=2969
    metadata: 
    encoded: vortex.pco(u32, len=16384) nbytes=2969
      metadata: ptype: u32, nrows: 16384, slice: 0..16384
  offsets: vortex.pco(u16, len=4067) nbytes=1465
    metadata: ptype: u16, nrows: 4067, slice: 0..4067

The issues seems to originate from the fact that delta scheme estimation overlooks the bases cost:

// Bits needed to FoR-pack the residuals. A zero span means constant deltas, which
// SequenceScheme already captures more cheaply, so defer to it.
let delta_bits = match span.checked_ilog2() {
Some(l) => (l + 1) as f64,
None => return Ok(EstimateVerdict::Skip),
};
let ratio = full_width / delta_bits * DELTA_PENALTY;

  1. FastLanes keeps one base per lane per chunk, and a chunk of W-bit values has 1024/W lanes, so each chunk's bases occupy 1024/W × W = 1024 bits — exactly one bit per stored element, independent of the value width.
  2. tail padding: delta_compress rounds up to whole 1024-value chunks, so the deltas child is len.next_multiple_of(1024) long, not len.

Steps to reproduce

Please check list_of_int_runs snapshot results in #9557

Environment

  • vortex = { version = "0.85", features = ["unstable_encodings"] }
  • vortex-btrblocks = { version = "0.85", features = ["unstable_encodings"] }

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA bug issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions