Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: impl canonicalize_into for SparseArray #2420

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

a10y
Copy link
Contributor

@a10y a10y commented Feb 19, 2025

Timer precision: 41 ns
canonical             fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ canonicalize_into                │               │               │               │         │
│  ├─ 0.001           8.207 µs      │ 553 µs        │ 11.77 µs      │ 12.59 µs      │ 1000    │ 1000
│  ├─ 0.01            12.04 µs      │ 121.5 µs      │ 13.2 µs       │ 13.42 µs      │ 1000    │ 1000
│  ├─ 0.05            15.99 µs      │ 38.08 µs      │ 18.41 µs      │ 18.43 µs      │ 1000    │ 1000
│  ╰─ 0.1             20.41 µs      │ 140.4 µs      │ 23.29 µs      │ 23.35 µs      │ 1000    │ 1000
╰─ into_canonical                   │               │               │               │         │
   ├─ 0.001           8.874 µs      │ 154.9 µs      │ 12.24 µs      │ 12.88 µs      │ 1000    │ 1000
   ├─ 0.01            11.91 µs      │ 129.2 µs      │ 13.12 µs      │ 13.38 µs      │ 1000    │ 1000
   ├─ 0.05            13.74 µs      │ 117.8 µs      │ 18.24 µs      │ 17.31 µs      │ 1000    │ 1000
   ╰─ 0.1             17.08 µs      │ 118.9 µs      │ 20.22 µs      │ 20.3 µs       │ 1000    │ 1000

canonicalize_primitive_into(&array, builder)?;
});
}
_ => unreachable!("unsupported SparseArray dtype {}", array.dtype()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably error here instead of panic, just because there's no compile-time safety that Sparse arrays don't one day start supporting other DTypes. I don't see why they couldn't, e.g. support ExtDType?

Could even fall back to builder.extend(array.into_canonical())

.fill_scalar()
.as_primitive()
.typed_value()
.vortex_expect("fill value");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is optional when the fill value is null. I don't think you can expect this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are correct 🤦

.fill(MaybeUninit::new(fill_value));
// SAFETY: we just filled the buffer with the fill value.
unsafe {
builder.values.set_len(sparse.len());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit, but putting the semi-colon after the } keeps it on one line.

.vortex_expect("fill value");

builder
.values
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's odd to me that this is public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is a bit odd, I think Dan exposed it as part of his bitpacking canonicalize_into. I can change PrimitiveBuilder to expose a spare_capacity_mut instead

}
});

// Set the validity from the sparse array.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a shame after specifically patching values, to then canonicalize the validity into a BooleanBuffer when there may only be a couple of nulls.

Copy link
Member

@0ax1 0ax1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Primarily had a peek at the benchmarks. Looks good, some tiny style suggestions.

@@ -28,5 +28,11 @@ vortex-mask = { workspace = true }
vortex-scalar = { workspace = true }

[dev-dependencies]
divan = { workspace = true }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

use vortex_scalar::Scalar;
use vortex_sparse::SparseArray;

fn generate_sparse_array(len: usize, sparsity: f64) -> SparseArray {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny style nits:

  • could consider going ordering functions by call order (been told by @gatesn that's how we do it :)
    => main first, then the benchmarks, then the helper. I primarily care about the benchmarks being above the helpers
  • factoring out shared bench parametrization into const BENCH_ARGS, e.g. see encodings/fsst/benches/fsst_compress.rs

Not really crucial, more like to get the benchmarks into similar shape and easily human parseable due to consistency.

fn into_canonical(bencher: Bencher, sparsity: f64) {
bencher
.with_inputs(|| generate_sparse_array(64_000, sparsity))
.bench_local_values(|sparse_array| sparse_array.into_canonical().unwrap())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious on your take about bench_local_values vs bench_values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was sort of an arbitrary choice, I think it's probably fine to parallelize so can switch to bench_values

Comment on lines 67 to 153
match_each_integer_ptype!(indices.ptype(), |$I| {
let indices = indices.as_slice::<$I>();
for (&index, value) in indices.iter().zip(values.boolean_buffer().into_iter()) {
builder.inner.set_bit(index as usize, value);
}
});

// Set the validity from the sparse array.
builder.nulls.append_validity_mask(sparse.validity_mask()?);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we move this into the builder?

builder
.values
.spare_capacity_mut()
.fill(MaybeUninit::new(fill_value));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this not fill past the arr len? Is that performance or correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, I can just trim this to [..len] and that's probably better

@joseph-isaacs
Copy link
Member

Did sparse show up as slow to canonicalize?

@a10y a10y marked this pull request as draft February 19, 2025 14:33
@a10y
Copy link
Contributor Author

a10y commented Feb 19, 2025

Moving to draft to clean things up.

@joseph-isaacs We use Sparse as a standalone compressor in vortex-btrblocks so I'd expect it to be more frequent in our encoding trees

@a10y a10y force-pushed the aduffy/sparse-canonicalize-into branch from 4ef8d81 to ad9eb84 Compare February 20, 2025 23:11
Copy link

codspeed-hq bot commented Feb 20, 2025

CodSpeed Performance Report

Merging #2420 will degrade performances by 23.33%

Comparing aduffy/sparse-canonicalize-into (ad9eb84) with develop (174e5b5)

Summary

❌ 1 regressions
✅ 764 untouched benchmarks
🆕 8 new benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark BASE HEAD Change
🆕 canonicalize_into[0.001] N/A 309.9 µs N/A
🆕 canonicalize_into[0.01] N/A 329 µs N/A
🆕 canonicalize_into[0.05] N/A 410.8 µs N/A
🆕 canonicalize_into[0.1] N/A 509.7 µs N/A
🆕 into_canonical[0.001] N/A 310.4 µs N/A
🆕 into_canonical[0.01] N/A 325.2 µs N/A
🆕 into_canonical[0.05] N/A 387.4 µs N/A
🆕 into_canonical[0.1] N/A 474.3 µs N/A
compress[(ALPRDCompressor, F32)] 35.4 ms 46.2 ms -23.33%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants