Skip to content

Commit fefe2e6

Browse files
committed
GH-47377: Extract dense selection execution
1 parent 57b20df commit fefe2e6

7 files changed

Lines changed: 153 additions & 54 deletions

File tree

cpp/src/arrow/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ set(ARROW_COMPUTE_SRCS
722722
compute/api_vector.cc
723723
compute/cast.cc
724724
compute/exec.cc
725+
compute/exec_selection.cc
725726
compute/expression.cc
726727
compute/function.cc
727728
compute/function_internal.cc

cpp/src/arrow/compute/exec.cc

Lines changed: 12 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,8 @@ bool ExecSpanIterator::Next(ExecSpan* span, SelectionSpan* selection_span) {
477477
// Then the selection span
478478
if (selection_vector_) {
479479
DCHECK_NE(selection_span, nullptr);
480-
const uint64_t chunk_start = static_cast<uint64_t>(position_);
481-
const uint64_t chunk_end =
482-
static_cast<uint64_t>(position_) + static_cast<uint64_t>(iteration_size);
480+
const int64_t chunk_start = position_;
481+
const int64_t chunk_end = position_ + iteration_size;
483482

484483
const int64_t consumed = selection_vector_->GetSpanForChunk(
485484
chunk_start, chunk_end, selection_position_, selection_span);
@@ -892,12 +891,6 @@ class ScalarExecutor : public KernelExecutorImpl<ScalarKernel> {
892891
return EmitResult(result->data(), listener);
893892
}
894893

895-
if (batch.selection_vector && !kernel_->selective_exec) {
896-
// If the batch contains a selection vector but the kernel does not support
897-
// selective execution, we need to execute the batch in a "dense" manner.
898-
return ExecuteSelectiveDense(batch, listener);
899-
}
900-
901894
return ExecuteBatch(batch, listener);
902895
}
903896

@@ -940,39 +933,6 @@ class ScalarExecutor : public KernelExecutorImpl<ScalarKernel> {
940933
}
941934
}
942935

943-
// Execute a single batch with a selection vector "densely" for a kernel that doesn't
944-
// support selective execution. "Densely" here means that we first gather the rows
945-
// indicated by the selection vector into a contiguous ExecBatch, execute that, and
946-
// then scatter the result back to the original row positions in the output.
947-
Status ExecuteSelectiveDense(const ExecBatch& batch, ExecListener* listener) {
948-
DCHECK(batch.selection_vector && !kernel_->selective_exec);
949-
950-
if (CheckIfAllScalar(batch)) {
951-
// For all-scalar batch, we can skip the gather/scatter steps as if there is no
952-
// selection vector - the result is a scalar anyway.
953-
ExecBatch input = batch;
954-
input.selection_vector = nullptr;
955-
return ExecuteBatch(input, listener);
956-
}
957-
958-
ARROW_ASSIGN_OR_RAISE(
959-
std::vector<Datum> values,
960-
batch.selection_vector->MakeDenseValues(batch.values, exec_context()));
961-
ARROW_ASSIGN_OR_RAISE(
962-
ExecBatch input,
963-
ExecBatch::Make(std::move(values), batch.selection_vector->length()));
964-
965-
DatumAccumulator dense_listener;
966-
RETURN_NOT_OK(ExecuteBatch(input, &dense_listener));
967-
Datum dense_result = WrapResults(input.values, dense_listener.values());
968-
969-
ARROW_ASSIGN_OR_RAISE(
970-
auto result,
971-
batch.selection_vector->ScatterDenseResult(dense_result, batch.length,
972-
exec_context()));
973-
return listener->OnResult(std::move(result));
974-
}
975-
976936
Status EmitResult(std::shared_ptr<ArrayData> out, ExecListener* listener) {
977937
if (span_iterator_.have_all_scalars()) {
978938
// ARROW-16757 We boxed scalar inputs as ArraySpan, so now we have to
@@ -1569,7 +1529,7 @@ void PropagateNullsSpans(const ExecSpan& batch, ArraySpan* out) {
15691529
}
15701530

15711531
std::unique_ptr<KernelExecutor> KernelExecutor::MakeScalar() {
1572-
return std::make_unique<detail::ScalarExecutor>();
1532+
return MakeDenseSelectionExecutor(std::make_unique<detail::ScalarExecutor>());
15731533
}
15741534

15751535
std::unique_ptr<KernelExecutor> KernelExecutor::MakeVector() {
@@ -1708,26 +1668,27 @@ class IndexSelectionVector final : public SelectionVector {
17081668
return Scatter(dense_result, indices, ScatterOptions{/*max_index=*/output_length - 1});
17091669
}
17101670

1711-
int64_t GetSpanForChunk(uint64_t chunk_start, uint64_t chunk_end,
1671+
int64_t GetSpanForChunk(int64_t chunk_start, int64_t chunk_end,
17121672
int64_t selection_position,
17131673
SelectionSpan* out) const override {
17141674
DCHECK_NE(out, nullptr);
1675+
DCHECK_GE(chunk_start, 0);
17151676
DCHECK_LE(chunk_start, chunk_end);
17161677

17171678
const int32_t* indices_begin = indices_ + selection_position;
17181679
const int32_t* indices_end = indices_ + length();
17191680
DCHECK_LE(indices_begin, indices_end);
17201681

1721-
const int32_t chunk_end_i32 = static_cast<int32_t>(chunk_end);
1722-
const int32_t* indices_limit =
1723-
std::lower_bound(indices_begin, indices_end, chunk_end_i32);
1682+
const int32_t* indices_limit = std::lower_bound(
1683+
indices_begin, indices_end, chunk_end,
1684+
[](int32_t index, int64_t end) { return static_cast<int64_t>(index) < end; });
17241685
const int64_t num_indices = indices_limit - indices_begin;
17251686

17261687
if (num_indices > 0) {
17271688
const int32_t first = indices_begin[0];
17281689
const int32_t last = indices_begin[num_indices - 1];
1729-
DCHECK_GE(static_cast<uint64_t>(first), chunk_start);
1730-
DCHECK_LT(static_cast<uint64_t>(last), chunk_end);
1690+
DCHECK_GE(static_cast<int64_t>(first), chunk_start);
1691+
DCHECK_LT(static_cast<int64_t>(last), chunk_end);
17311692

17321693
// If the discrete indices form a contiguous run, represent them as such.
17331694
// Since Validate enforces strict increasing order, checking
@@ -1737,10 +1698,10 @@ class IndexSelectionVector final : public SelectionVector {
17371698
static_cast<int64_t>(chunk_start),
17381699
num_indices};
17391700
} else {
1740-
*out = DiscreteSpan{indices_begin, num_indices, static_cast<int32_t>(chunk_start)};
1701+
*out = DiscreteSpan{indices_begin, num_indices, chunk_start};
17411702
}
17421703
} else {
1743-
*out = DiscreteSpan{indices_begin, /*length=*/0, static_cast<int32_t>(chunk_start)};
1704+
*out = DiscreteSpan{indices_begin, /*length=*/0, chunk_start};
17441705
}
17451706

17461707
return num_indices;

cpp/src/arrow/compute/exec.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,10 @@ struct ARROW_EXPORT FilteredSpan {
155155
struct ARROW_EXPORT DiscreteSpan {
156156
const int32_t* indices = NULLPTR;
157157
int64_t length = 0;
158-
int32_t index_back_shift = 0;
158+
int64_t index_back_shift = 0;
159159

160160
int64_t operator[](int64_t i) const {
161-
return static_cast<int64_t>(indices[i] - index_back_shift);
161+
return static_cast<int64_t>(indices[i]) - index_back_shift;
162162
}
163163
};
164164

@@ -224,7 +224,7 @@ class ARROW_EXPORT SelectionVector {
224224
/// in earlier chunks (rank).
225225
/// \param[out] out Selection span relative to chunk_start.
226226
/// \return Number of selected indices consumed from this chunk.
227-
virtual int64_t GetSpanForChunk(uint64_t chunk_start, uint64_t chunk_end,
227+
virtual int64_t GetSpanForChunk(int64_t chunk_start, int64_t chunk_end,
228228
int64_t selection_position,
229229
SelectionSpan* out) const = 0;
230230
};

cpp/src/arrow/compute/exec_internal.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ class ARROW_EXPORT KernelExecutor {
156156
static std::unique_ptr<KernelExecutor> MakeScalarAggregate();
157157
};
158158

159+
/// \brief Wrap a scalar kernel executor with dense selection fallback.
160+
///
161+
/// Batches without a selection, and selections handled natively by the kernel, are
162+
/// delegated unchanged. For kernels without selective execution, selected inputs are
163+
/// gathered before delegation and the dense result is scattered back afterwards.
164+
std::unique_ptr<KernelExecutor> MakeDenseSelectionExecutor(
165+
std::unique_ptr<KernelExecutor> executor);
166+
159167
ARROW_EXPORT
160168
int64_t InferBatchLength(const std::vector<Datum>& values, bool* all_same);
161169

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#include "arrow/compute/exec_internal.h"
19+
20+
#include <memory>
21+
#include <utility>
22+
#include <vector>
23+
24+
#include "arrow/compute/kernel.h"
25+
#include "arrow/datum.h"
26+
#include "arrow/status.h"
27+
#include "arrow/util/logging_internal.h"
28+
29+
namespace arrow::compute::detail {
30+
31+
namespace {
32+
33+
bool CheckIfAllScalar(const ExecBatch& batch) {
34+
for (const Datum& value : batch.values) {
35+
if (!value.is_scalar()) {
36+
return false;
37+
}
38+
}
39+
return batch.num_values() > 0;
40+
}
41+
42+
class DenseSelectionExecutor : public KernelExecutor {
43+
public:
44+
explicit DenseSelectionExecutor(std::unique_ptr<KernelExecutor> executor)
45+
: executor_(std::move(executor)) {
46+
DCHECK_NE(executor_, nullptr);
47+
}
48+
49+
Status Init(KernelContext* kernel_ctx, KernelInitArgs args) override {
50+
kernel_ = static_cast<const ScalarKernel*>(args.kernel);
51+
exec_context_ = kernel_ctx->exec_context();
52+
return executor_->Init(kernel_ctx, args);
53+
}
54+
55+
Status Execute(const ExecBatch& batch, ExecListener* listener) override {
56+
DCHECK_NE(kernel_, nullptr);
57+
58+
// Preserve the scalar executor's zero-length handling. A selection cannot make a
59+
// zero-length input any denser.
60+
if (batch.length == 0 || !batch.selection_vector || kernel_->selective_exec) {
61+
return executor_->Execute(batch, listener);
62+
}
63+
64+
if (CheckIfAllScalar(batch)) {
65+
// The result is scalar regardless of the selection, so gathering and scattering
66+
// would only box an otherwise scalar result.
67+
ExecBatch input = batch;
68+
input.selection_vector = nullptr;
69+
return executor_->Execute(input, listener);
70+
}
71+
72+
ARROW_ASSIGN_OR_RAISE(
73+
std::vector<Datum> values,
74+
batch.selection_vector->MakeDenseValues(batch.values, exec_context_));
75+
ARROW_ASSIGN_OR_RAISE(
76+
ExecBatch input,
77+
ExecBatch::Make(std::move(values), batch.selection_vector->length()));
78+
79+
DatumAccumulator dense_listener;
80+
RETURN_NOT_OK(executor_->Execute(input, &dense_listener));
81+
Datum dense_result = executor_->WrapResults(input.values, dense_listener.values());
82+
83+
ARROW_ASSIGN_OR_RAISE(
84+
Datum result,
85+
batch.selection_vector->ScatterDenseResult(dense_result, batch.length,
86+
exec_context_));
87+
return listener->OnResult(std::move(result));
88+
}
89+
90+
Datum WrapResults(const std::vector<Datum>& args,
91+
const std::vector<Datum>& outputs) override {
92+
return executor_->WrapResults(args, outputs);
93+
}
94+
95+
Status CheckResultType(const Datum& out, const char* function_name) override {
96+
return executor_->CheckResultType(out, function_name);
97+
}
98+
99+
private:
100+
std::unique_ptr<KernelExecutor> executor_;
101+
const ScalarKernel* kernel_ = nullptr;
102+
ExecContext* exec_context_ = nullptr;
103+
};
104+
105+
} // namespace
106+
107+
std::unique_ptr<KernelExecutor> MakeDenseSelectionExecutor(
108+
std::unique_ptr<KernelExecutor> executor) {
109+
return std::make_unique<DenseSelectionExecutor>(std::move(executor));
110+
}
111+
112+
} // namespace arrow::compute::detail

cpp/src/arrow/compute/exec_test.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,22 @@ TEST(SelectionVector, Validate) {
194194
}
195195
}
196196

197+
TEST(SelectionVector, GetSpanForChunkAtInt32Limit) {
198+
auto selection =
199+
SelectionVectorFromJSON("[" + std::to_string(std::numeric_limits<int32_t>::max()) +
200+
"]");
201+
SelectionSpan span;
202+
const int64_t chunk_end =
203+
static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 1;
204+
205+
ASSERT_EQ(1, selection->GetSpanForChunk(/*chunk_start=*/0, chunk_end,
206+
/*selection_position=*/0, &span));
207+
const auto* contiguous = std::get_if<ContiguousSpan>(&span);
208+
ASSERT_NE(contiguous, nullptr);
209+
ASSERT_EQ(std::numeric_limits<int32_t>::max(), contiguous->start_offset);
210+
ASSERT_EQ(1, contiguous->length);
211+
}
212+
197213
TEST(DiscreteSpan, Basics) {
198214
auto indices = ArrayFromJSON(int32(), "[0, 3, 7]");
199215
const int32_t* idx = indices->data()->GetValues<int32_t>(1);

cpp/src/arrow/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ arrow_components = {
5252
'compute/api_vector.cc',
5353
'compute/cast.cc',
5454
'compute/exec.cc',
55+
'compute/exec_selection.cc',
5556
'compute/expression.cc',
5657
'compute/function.cc',
5758
'compute/function_internal.cc',

0 commit comments

Comments
 (0)