Add UnionValue and UnionScalar#8838
Conversation
Polar Signals Profiling ResultsLatest Run
Previous Runs (2)
Powered by Polar Signals Cloud |
Benchmarks: Vortex queries 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.861x ✅, 2↑ 0↓)
datafusion / parquet (0.834x ✅, 2↑ 0↓)
duckdb / vortex-file-compressed (0.985x ➖, 0↑ 0↓)
duckdb / parquet (0.960x ➖, 0↑ 0↓)
No file size changes detected. |
Merging this PR will degrade performance by 17.18%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
dd92c91 to
fd2e580
Compare
| let values = match dtype { | ||
| DType::List(element_dtype, _) | DType::FixedSizeList(element_dtype, ..) => v | ||
| .values | ||
| .iter() | ||
| .map(|elem| ScalarValue::from_proto(elem, element_dtype.as_ref(), session)) | ||
| .collect::<VortexResult<Vec<_>>>()?, | ||
| DType::Struct(fields, _) => { | ||
| vortex_ensure_eq!( | ||
| v.values.len(), fields.nfields(), | ||
| Serde: "expected {} struct fields in ListValue, got {}", | ||
| fields.nfields(), | ||
| v.values.len() | ||
| ); | ||
|
|
||
| v.values | ||
| .iter() | ||
| .zip(fields.fields()) | ||
| .map(|(value, field_dtype)| ScalarValue::from_proto(value, &field_dtype, session)) | ||
| .collect::<VortexResult<Vec<_>>>()? | ||
| } |
There was a problem hiding this comment.
This was an existing "bug" (just how we display things), I just fixed it here.
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
fd2e580 to
5e0113d
Compare
| match dtype { | ||
| DType::Null => vortex_panic!("Null dtype has no zero value"), | ||
| Self::try_zero_value(dtype) | ||
| .unwrap_or_else(|| vortex_panic!("{dtype} has no non-null zero value")) |
There was a problem hiding this comment.
Call zero value on a scalar I would never expect to fail
There was a problem hiding this comment.
we already panic here for null though?
There was a problem hiding this comment.
I pointed this out on the other pr that this pr recreates that the change here is unnecessary noise. It's an internal helper function so why do we change it. We have added DType::Null for arrow compatibility and I think we shouldn't have
There was a problem hiding this comment.
Given that we were considering vortex -> arrow union exporting via adding a null variant to the union to support our "outer null" conversion I think we have a good enough reason to keep it.
So it would be helpful for me if we decided if the semantics that I summarized in the PR description are the ones we want, rather than debating if the internal APIs that are affected by this are maybe unexpected (XY problem).
There was a problem hiding this comment.
I thought that we had discussed this on Thursday and agreed that we want to have outer nulls and potentially add variant on export to arrow. I am commenting that I don't see how these two things are related and changes to this method seem unnecessary to me. Maybe I am missing something more fundamental here? If you removed the non throwing variant that would make more sense but yet again unless you do all of that the changes here are weird
There was a problem hiding this comment.
lets try and meet tomorrow because I honestly do not understand what the issue is here, and there are other comments here that imply we are not all on the same page.
For this specific thing, is the issue the implementation or the internal API? I've seen the comments on how the API might be strange but, like I said above, we haven't actually talked about the thing that caused me to make that change, and so I don't understand understand the reasoning why this needs to be "fixed". It feels like we are talking past each other.
| Self::Tuple(field_values) | ||
| } | ||
| DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"), | ||
| DType::Union(..) => return None, |
There was a problem hiding this comment.
What do you want this to be
There was a problem hiding this comment.
I dont understand this question. Are you asking about why this is None? I explain at the bottom of the PR description
## Rationale for this change - Closes: #8807 Three benchmarks stayed flaky after #8742, flipping between the same two values on PRs that can't affect them. Same root causes and fixes as #8742: | Benchmark | Seen flaky on | Why | Fix | | --- | --- | --- | --- | | `true_count_vortex_buffer[128]` | ±11.17% on 9 unrelated PRs (#8805, #8811, #8812, #8820, #8843, #8803, …) | a 128-bit popcount measures harness overhead and code layout, not the count | drop the 128 size | | runend `compress[(100000, 4)]` | ±11.9% on #8805, #8750, #8856 | allocates in the timed region; glibc malloc differs across runner images | mimalloc as global allocator | | `cast_decimal` `copy_*[65536]` | identical flags on #8838 and #8724 | same glibc-malloc cause (512 KB alloc per iteration) | mimalloc as global allocator | Left alone: `compact_sliced[(4096, 90)]` (single sighting) and the CUDA walltime benches (hosted-runner walltime noise, a runner config issue). The allocator swap shifts every benchmark in the two touched binaries once — see the comment below. Needs a one-time CodSpeed acknowledgment, like #8742. ## What changes are included in this PR? One commit per benchmark; bench files only. Ran `cargo check` + `clippy` on the three bench targets, smoke-ran the binaries, `cargo +nightly fmt`. --------- Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
Revived from #8791 since too many things have changed.
Summary
Tracking issue: #8769
Adds scalar implementation for
Uniontype in Vortex.Changes
Adds
UnionValuewhich exists inside ofScalarValueand the typed viewUnionScalar<'a>.Inner Nulls with Type IDs
The type above implies that type IDs MATTER for equality of nulls. 2 values with the same union type might both be null, but if the type IDs are different, we consider them NOT equal. Please respond below if you think this is the incorrect behavior. I think that this might be different from arrow's semantics, but because we are already distinguishing from them with the outer nullability, I think this is fine.
Value type
I was debating if we should instead store aOption<Box<ScalarValue>>instead of justBox<Scalar>(which carries an extra dtype and an extra allocation even if the value is null). I think it is nice to have the dtype in there for implementation ease. Not sure if it's worth it.We use
Option<Box<ScalarValue>>instead ofBox<Scalar>so that we do not storeDTypein multiple places. If we did so, we run into synchronization issues with casting.Casting
For casting, this only allows identity and null casting (like everything else), otherwise it just panics with unimplemented. We probably want to figure this out later as it is quite valuable to cast union types.
Default and Zero value semantics
Finally, the "default value" and "zero value" semantics here are as such:
default_value(nullable Union)-> returns an outer null.default_value(non-nullable Union)-> panics because there is no designated default variant.zero_value(Union)-> panics because a Union has no canonical zero variant.is_zero()on a null Union -> returnsNone.is_zero()on a valid Union -> returnsSome(false), even when its child is numerically zero.Note that none of these behaviors depend on variant ordering. And also I do think that it is possible that we just choose the first variant of a union to be the default, I think this is kind of opinionated, and it also doesn't solve the issue where there are 0 variants (empty union / void type). So I would prefer if we do this in a separate change.