Uh oh!
There was an error while loading. Please reload this page.
ARROW-13573: [C++] Support dictionaries natively in case_when - #11022
ARROW-13573: [C++] Support dictionaries natively in case_when#11022lidavidm wants to merge 21 commits into
Conversation
9746270 to
7d3aeaeComparef6a7a85 to
a93ce99Comparelidavidm
commented
Aug 31, 2021
One thought: we could have all dictionary types use the variable-width type implementation, meaning we'd always unify dictionaries. This would behave a little more consistently. |
pitrou
left a comment
There was a problem hiding this comment.
Some comments. I haven't looked fully at the implementation yet.
There was a problem hiding this comment.
Do we really want to do this check every append or should this be left to callers?
There was a problem hiding this comment.
What happens if dict has a null at this index?
There was a problem hiding this comment.
Hmm, there should be better testing for nulls in general, I'll amend that.
There was a problem hiding this comment.
Is it possible to factor this out to avoid repetition? For example:
template <IndexType>
structSliceAppender {
const IndexType* values;
Status operator()(const ArrayData& array, int64_t offset, int64_t length) {
returnVisitBitBlocks(
array.buffers[0], array.offset + offset, length,
[&](int64_t position) {
if (dict.IsNull(values[position])) returnAppendNull();
returnAppend(dict.GetView(values[position]));
},
[&]() { returnAppendNull(); }); }
);
}
}
case Type::UINT8:
return SliceAppender{array.GetValues<uint8_t>(1) + offset}(array, offset, length);
// ...Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Other AppendArraySlice implementations don't check that length is in bounds, so std::min doesn't seem necessary here.
There was a problem hiding this comment.
Why is this calling CheckScalarNonRecursive and not CheckScalar? Leave a comment?
There was a problem hiding this comment.
The scalar variant of the kernel will not produce the same dictionary indices so the values do not compare equal. I'll add a comment to that effect.
There was a problem hiding this comment.
For some reason, it looks like the nulls in the indices are placed at the same indices as the nulls in the respective dictionaries.
There was a problem hiding this comment.
I'm curious: why don't we unify dictionaries instead? It would sound more useful to me. I don't see any reason for the first input to have a particular status, is there?
There was a problem hiding this comment.
I had mostly tried to emulate the R/dplyr behavior as closely as possible: #10724 (comment)
But unification is honestly probably easier to implement for us, so I can switch to that instead.
There was a problem hiding this comment.
I'm not sure I understand what this TODO is for. Emitting a null when some option is enabled?
There was a problem hiding this comment.
Don't we already have DictionaryUnifier for this? Or am I misunderstanding?
There was a problem hiding this comment.
This is for if we don't want unification, however, I think we might want to just unify dictionaries always.
IIRC, what DictionaryUnifier was missing was a way to compute a transposition map without adding new values to the internal memo table.
lidavidm
commented
Sep 7, 2021
Changes:
|
lidavidm
commented
Sep 9, 2021
It looks like the RTools 40 test failure/crash is real; I'm going to need to figure out how to replicate this properly. (So far I've had little success with a VM, unfortunately.) |
lidavidm
commented
Sep 13, 2021
It looks like there's still 2 Windows failures to look into (a segfault in MinGW/32, which is hopefully more debuggable, and a failure to run one of the examples in RTools35), though the RTools40 crash is no more. |
pitrou
commented
Sep 13, 2021
Hmm... did you make sense of the RTools 3.5 CI failure? I can't find the actual error in the logs :-/ |
lidavidm
commented
Sep 13, 2021
I'm setting up my Windows VM again since it expires every few months now :/ but it seems like the dataset example crashed when it was run, looking here: https://github.com/apache/arrow/pull/11022/checks?check_run_id=3589141006#step:12:395 |
pitrou
commented
Sep 13, 2021
Is that example expected to be impacted by this PR? Otherwise, perhaps we should just restart the build... |
lidavidm
commented
Sep 13, 2021
I do not expect it to be impacted but it was also failing in the last couple builds. (That said, I think it wasn't failing before I turned off the unity build?) |
lidavidm
commented
Sep 13, 2021
And I did finally get the R package built on Windows - |
lidavidm
commented
Sep 16, 2021
The CI is passing here, for the first time in quite a while. |
| export ARROW_S3=ON | ||
| export ARROW_WITH_RE2=ON | ||
| # Without this, some compute functionality segfaults | ||
| export CMAKE_UNITY_BUILD=OFF |
There was a problem hiding this comment.
You mean it segfaults during compilation?
There was a problem hiding this comment.
It segfaults in the tests. I wasn't really able to debug this on Windows; it disappears once you build with debuginfo.
| if (index_scalar.is_valid && dict.IsValid(index)) { | ||
| const auto& value = dict.GetView(index); | ||
| for (int64_t i = 0; i < n_repeats; i++) { | ||
| ARROW_RETURN_NOT_OK(Append(value)); |
There was a problem hiding this comment.
Not for this PR, but it sounds like offering a two-step API on DictionaryBuilder would allow for performance improvements:
/// Ensure `value` is in the dict, and return its index, but doesn't append it
Result<int64_t> Encode(c_type value);
/// Append the given dictionary index
Status AppendIndex(int64_t index);
Status AppendIndices(int64_t index, int64_t nrepeats);| } | ||
| EXPECT_OK_AND_ASSIGN(Datum expected, CallFunction(func_name, decoded_args)); | ||
| if (actual.type()->id() == Type::DICTIONARY) { |
There was a problem hiding this comment.
Hmm, it would be nice if the caller actually said whether the output is supposed to be dictionary-encoded or not. Otherwise there could be silent regressions where the output type of a kernel changes from one version to another.
There was a problem hiding this comment.
Do you mean add an options struct?
There was a problem hiding this comment.
CheckDictionary could accept an argument saying if the expected output is dictionary-encoded or not.
There was a problem hiding this comment.
Ah I see what you mean now - will do.
| for (auto index : {"null", "2", "1", "0"}) { | ||
| auto scalar = DictScalarFromJSON(type, index, dict); | ||
| auto expected_index = ScalarFromJSON(int32(), index); | ||
| AssertScalarsEqual(*DictionaryScalar::Make(expected_index, expected_dictionary), |
This supports dictionaries 'natively', that is, dictionaries are no longer always unpacked. (If mixed dictionary and non-dictionary arguments are given, then they will be unpacked.) For scalar conditions, the output will have the dictionary of whichever input is selected (or no dictionary if the output is null). For array conditions, we unify the dictionaries as we select elements. Closesapache#11022 from lidavidm/arrow-13573 Authored-by: David Li <li.davidm96@gmail.com> Signed-off-by: Antoine Pitrou <antoine@python.org>
This supports dictionaries 'natively', that is, dictionaries are no longer always unpacked. (If mixed dictionary and non-dictionary arguments are given, then they will be unpacked.)
For scalar conditions, the output will have the dictionary of whichever input is selected (or no dictionary if the output is null). For array conditions, we unify the dictionaries as we select elements.