zanmato1984 commented on code in PR #55169:
URL: https://github.com/apache/arrow/pull/55169#discussion_r4428954799
##########
cpp/src/arrow/compute/kernels/vector_swizzle_test.cc:
##########
@@ -192,6 +217,48 @@ TEST(InversePermutation, InvalidIndex) {
}
}
+TEST(InversePermutation, IndexBounds) {
+ for (const auto& type : IntTypes()) {
+ ARROW_SCOPED_TRACE(type->ToString());
+ const std::string invalid_index = is_signed_integer(type->id()) ? "-1" :
"42";
+ for (const Datum& indices :
+ {Datum(ArrayFromJSON(type, "[" + invalid_index + "]")),
+ Datum(ChunkedArrayFromJSON(type, {"[]", "[" + invalid_index +
"]"}))}) {
+ ASSERT_RAISES_WITH_MESSAGE(
+ IndexError, "Index error: Index out of bounds: " + invalid_index,
+ InversePermutation(indices, /*max_index=*/1,
/*output_type=*/int64()));
Review Comment:
Changed to `max_index = 41`, so index `42` is exactly one past the allowed
maximum. The negative-index and large-`uint64` checks are preserved.
##########
cpp/src/arrow/compute/kernels/vector_swizzle_test.cc:
##########
@@ -120,29 +120,54 @@ void TestInversePermutationForInputOutputTypes(
void TestInversePermutation(const std::vector<std::string>&
indices_chunked_str,
int64_t max_index, const std::string& expected_str,
bool validity_must_be_null = false) {
- TestInversePermutationForInputOutputTypes(SignedIntTypes(), SignedIntTypes(),
- indices_chunked_str, max_index,
expected_str,
+ TestInversePermutationForInputOutputTypes(IntTypes(), IntTypes(),
indices_chunked_str,
+ max_index, expected_str,
validity_must_be_null);
}
} // namespace
-TEST(InversePermutation, InvalidOutputType) {
- {
- ARROW_SCOPED_TRACE("Output type unsigned");
- auto indices = ArrayFromJSON(int32(), "[]");
- ASSERT_RAISES_WITH_MESSAGE(
- TypeError,
- "Type error: Output type of inverse_permutation must be signed
integer, got "
- "uint32",
- InversePermutation(indices, /*max_index=*/0,
/*output_type=*/uint32()));
+TEST(InversePermutation, InvalidInputType) {
+ // The integer matcher must not accept non-integers of the same width.
+ for (const auto& type : {boolean(), float32(), float64(), utf8()}) {
+ ARROW_SCOPED_TRACE(type->ToString());
+ ASSERT_RAISES(NotImplemented, InversePermutation(ArrayFromJSON(type,
"[null]")));
+ }
+}
+
+TEST(InversePermutation, DenseValidityBitmapBoundaries) {
+ for (int64_t length : {7, 8, 9, 63, 64, 65}) {
Review Comment:
Added an example in the test. For `length = 8` and `missing = 3`, indices
`[0, 1, 2, 4, 5, 6, 7]` produce `[0, 1, 2, null, 3, 4, 5, 6]`. The selected
lengths and missing positions exercise byte/word boundaries and unaligned
bitmap writes.
##########
cpp/src/arrow/compute/kernels/vector_swizzle.cc:
##########
@@ -251,6 +306,16 @@ struct InversePermutation {
template <typename ValidFunc, typename NullFunc>
static Status VisitIndices(const ArraySpan& span, ValidFunc&& valid_func,
NullFunc&& null_func) {
+ // Avoid per-block validity dispatch when every input index is valid.
+ if (!span.MayHaveNulls()) {
Review Comment:
Switched to `VisitBitRuns`, removing the separate no-null loop. In isolated
inverse-permutation benchmarks on Apple M4 with Clang/O2, the traversal change
gave about 1.5–1.9× speedup with 50% random nulls, but about 2.7× runtime with
strictly alternating nulls. The unified traversal therefore has a known
performance trade-off.
##########
cpp/src/arrow/compute/kernels/vector_swizzle.cc:
##########
@@ -44,24 +49,64 @@ const InversePermutationOptions*
GetDefaultInversePermutationOptions() {
using InversePermutationState = OptionsWrapper<InversePermutationOptions>;
/// Resolve the output type of inverse_permutation. The output type is
specified in the
-/// options, and if null, set it to the input type. The output type must be
signed
-/// integer.
+/// options, and if null, set it to the input type. The output type must be
integer.
Result<TypeHolder> ResolveInversePermutationOutputType(
KernelContext* ctx, const std::vector<TypeHolder>& input_types) {
DCHECK_EQ(input_types.size(), 1);
DCHECK_NE(input_types[0], nullptr);
std::shared_ptr<DataType> output_type =
InversePermutationState::Get(ctx).output_type.value_or(input_types[0].owned_type);
- if (!is_signed_integer(output_type->id())) {
- return Status::TypeError(
- "Output type of inverse_permutation must be signed integer, got " +
- output_type->ToString());
+ if (!is_integer(output_type->id())) {
+ return Status::TypeError("Output type of inverse_permutation must be
integer, got " +
+ output_type->ToString());
}
return TypeHolder(std::move(output_type));
}
+// Share the input implementation for signed and unsigned integers of the same
width.
+// The logical types still determine bounds checking and the output array type.
+template <typename Type>
+using UnsignedIntegerType =
+ typename CTypeTraits<std::make_unsigned_t<typename
Type::c_type>>::ArrowType;
+
+// This pass depends only on the output width, not the index type or input
shape.
+// Keep it separate from the indexed writes, and generate whole bitmap bytes
+// instead of clearing one bit at a time for every missing destination.
+template <typename OutputCType>
+ARROW_NOINLINE Status InversePermutationValidity(KernelContext* ctx,
+ const OutputCType* data,
+ int64_t input_length,
+ int64_t output_length,
+ std::shared_ptr<Buffer>*
validity_buf) {
+ if (output_length == 0) {
+ return Status::OK();
+ }
+ const auto invalid = static_cast<OutputCType>(input_length);
+ const auto* first_invalid = std::find(data, data + output_length, invalid);
+ const int64_t offset = first_invalid - data;
+ if (offset == output_length) {
+ return Status::OK();
+ }
+ ARROW_ASSIGN_OR_RAISE(*validity_buf, ctx->AllocateBitmap(output_length));
+ auto* validity = (*validity_buf)->mutable_data();
+ bit_util::SetBitmap(validity, 0, offset);
Review Comment:
This call sets the already-valid prefix `[0, offset)` to **ones**, rather
than zeroing it. The `0` is the starting bit offset. I kept the call and added
`/*offset=*/0` and `/*length=*/offset` to make that distinction explicit.
##########
cpp/src/arrow/compute/kernels/vector_swizzle_test.cc:
##########
@@ -192,6 +217,48 @@ TEST(InversePermutation, InvalidIndex) {
}
}
+TEST(InversePermutation, IndexBounds) {
+ for (const auto& type : IntTypes()) {
+ ARROW_SCOPED_TRACE(type->ToString());
+ const std::string invalid_index = is_signed_integer(type->id()) ? "-1" :
"42";
+ for (const Datum& indices :
+ {Datum(ArrayFromJSON(type, "[" + invalid_index + "]")),
+ Datum(ChunkedArrayFromJSON(type, {"[]", "[" + invalid_index +
"]"}))}) {
+ ASSERT_RAISES_WITH_MESSAGE(
+ IndexError, "Index error: Index out of bounds: " + invalid_index,
+ InversePermutation(indices, /*max_index=*/1,
/*output_type=*/int64()));
+ }
+ }
+ // Reading signed indices through their unsigned representation must still
reject
+ // negative values, even if that representation would fit in the output.
+ for (const auto& indices :
+ {ArrayFromJSON(int8(), "[-1]"), ArrayFromJSON(int8(), "[-128]")}) {
+ ASSERT_RAISES(IndexError, InversePermutation(indices, /*max_index=*/300,
+ /*output_type=*/int64()));
+ }
+ // Do not narrow uint64 indices to int64 before checking bounds.
+ for (const std::string index : {"9223372036854775808",
"18446744073709551615"}) {
+ for (const Datum& indices :
+ {Datum(ArrayFromJSON(uint64(), "[" + index + "]")),
+ Datum(ChunkedArrayFromJSON(uint64(), {"[]", "[" + index + "]"}))}) {
+ ASSERT_RAISES_WITH_MESSAGE(
+ IndexError, "Index error: Index out of bounds: " + index,
+ InversePermutation(indices, /*max_index=*/1,
/*output_type=*/int64()));
+ }
+ }
+}
+
+TEST(InversePermutation, SlicedIndices) {
+ for (const auto& type : IntTypes()) {
+ auto indices = ArrayFromJSON(type, "[42, 2, null, 0, 1, 42]")->Slice(1, 4);
+ auto expected = ArrayFromJSON(type, "[2, 3, 0, null]");
+ AssertInversePermutation(indices, -1, type, expected, false);
Review Comment:
Added the parameter-name comments to both calls, and checked the other new
tests for similar cases.
##########
cpp/src/arrow/compute/kernels/vector_swizzle.cc:
##########
@@ -44,24 +49,64 @@ const InversePermutationOptions*
GetDefaultInversePermutationOptions() {
using InversePermutationState = OptionsWrapper<InversePermutationOptions>;
/// Resolve the output type of inverse_permutation. The output type is
specified in the
-/// options, and if null, set it to the input type. The output type must be
signed
-/// integer.
+/// options, and if null, set it to the input type. The output type must be
integer.
Result<TypeHolder> ResolveInversePermutationOutputType(
KernelContext* ctx, const std::vector<TypeHolder>& input_types) {
DCHECK_EQ(input_types.size(), 1);
DCHECK_NE(input_types[0], nullptr);
std::shared_ptr<DataType> output_type =
InversePermutationState::Get(ctx).output_type.value_or(input_types[0].owned_type);
- if (!is_signed_integer(output_type->id())) {
- return Status::TypeError(
- "Output type of inverse_permutation must be signed integer, got " +
- output_type->ToString());
+ if (!is_integer(output_type->id())) {
+ return Status::TypeError("Output type of inverse_permutation must be
integer, got " +
+ output_type->ToString());
}
return TypeHolder(std::move(output_type));
}
+// Share the input implementation for signed and unsigned integers of the same
width.
+// The logical types still determine bounds checking and the output array type.
+template <typename Type>
+using UnsignedIntegerType =
+ typename CTypeTraits<std::make_unsigned_t<typename
Type::c_type>>::ArrowType;
+
+// This pass depends only on the output width, not the index type or input
shape.
+// Keep it separate from the indexed writes, and generate whole bitmap bytes
+// instead of clearing one bit at a time for every missing destination.
+template <typename OutputCType>
+ARROW_NOINLINE Status InversePermutationValidity(KernelContext* ctx,
Review Comment:
Changed to return `Result<std::shared_ptr<Buffer>>`, removing the output
parameter.
##########
cpp/src/arrow/compute/kernels/vector_swizzle.cc:
##########
@@ -296,28 +362,37 @@ struct InversePermutationChunked {
}
};
+template <typename Ignored, typename Type>
+using UnsignedInversePermutation = InversePermutation<Ignored,
UnsignedIntegerType<Type>>;
+
+template <typename Ignored, typename Type>
+using UnsignedInversePermutationChunked =
+ InversePermutationChunked<Ignored, UnsignedIntegerType<Type>>;
+
void RegisterVectorInversePermutation(FunctionRegistry* registry) {
auto function = std::make_shared<VectorFunction>("inverse_permutation",
Arity::Unary(),
inverse_permutation_doc,
GetDefaultInversePermutationOptions());
- auto add_kernel = [&function](Type::type type_id) {
- VectorKernel kernel;
- kernel.signature =
- KernelSignature::Make({InputType(match::SameTypeId(type_id))},
- OutputType(ResolveInversePermutationOutputType));
- kernel.init = InversePermutationState::Init;
- kernel.exec = GenerateInteger<InversePermutation, void,
ArrayKernelExec>(type_id);
- kernel.exec_chunked =
- GenerateInteger<InversePermutationChunked, void,
VectorKernel::ChunkedExec>(
- type_id);
- kernel.can_execute_chunkwise = false;
- kernel.output_chunked = false;
- DCHECK_OK(function->AddKernel(std::move(kernel)));
+ // Exact dispatch scans every kernel. Match integers once, then select the
+ // shared physical implementation without increasing per-call matching costs.
+ VectorKernel kernel;
+ kernel.signature = KernelSignature::Make(
+ {InputType(match::Integer())},
OutputType(ResolveInversePermutationOutputType));
+ kernel.init = InversePermutationState::Init;
+ kernel.exec = [](KernelContext* ctx, const ExecSpan& span, ExecResult*
result) {
+ const auto exec = GenerateInteger<UnsignedInversePermutation, void,
ArrayKernelExec>(
Review Comment:
That looks reasonable. I'll review #55291 and adapt this code to the shared
generator once it lands.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]