github-actions[bot] commented on code in PR #61613:
URL: https://github.com/apache/doris/pull/61613#discussion_r2973097284
##########
be/src/exec/operator/table_function_operator.cpp:
##########
@@ -160,6 +167,154 @@ bool TableFunctionLocalState::_is_inner_and_empty() {
return false;
}
+bool TableFunctionLocalState::_can_use_block_fast_path() const {
+ auto& p = _parent->cast<TableFunctionOperatorX>();
+ // Fast path is only valid when:
+ // - only one table function exists
+ // - there is an active child row to expand
+ // - the child block is non-empty
+ // - the table function can expose nested/offsets via
prepare_block_fast_path()
+ return p._fn_num == 1 && _cur_child_offset != -1 && _child_block->rows() >
0 &&
+ _fns[0]->support_block_fast_path();
+}
+
+Status TableFunctionLocalState::_get_expanded_block_block_fast_path(
+ RuntimeState* state, std::vector<MutableColumnPtr>& columns) {
+ auto& p = _parent->cast<TableFunctionOperatorX>();
+ // Fast path for explode-like table functions:
+ // Instead of calling TableFunction::get_value() row-by-row, copy the
nested column range
+ // directly when the nested values in this output batch are contiguous in
memory.
+ if (!_block_fast_path_prepared) {
+ RETURN_IF_ERROR(
+ _fns[0]->prepare_block_fast_path(_child_block.get(), state,
&_block_fast_path_ctx));
+ _block_fast_path_prepared = true;
+ _block_fast_path_row = _cur_child_offset;
+ _block_fast_path_in_row_offset = 0;
+ }
+
+ const auto remaining_capacity =
+ state->batch_size() -
cast_set<int>(columns[p._child_slots.size()]->size());
+ if (remaining_capacity <= 0) {
+ return Status::OK();
+ }
+
+ if (_block_fast_path_ctx.offsets_ptr == nullptr ||
+ _block_fast_path_ctx.nested_col.get() == nullptr) {
+ return Status::InternalError("block fast path context is invalid");
+ }
+
+ const auto& offsets = *_block_fast_path_ctx.offsets_ptr;
+ const auto child_rows = cast_set<int64_t>(offsets.size());
+ if (child_rows != cast_set<int64_t>(_child_block->rows())) {
+ return Status::InternalError("block fast path offsets size mismatch");
+ }
+
+ std::vector<uint32_t> row_ids;
+ row_ids.reserve(remaining_capacity);
Review Comment:
[Performance, Minor] `std::vector<uint32_t> row_ids` is allocated on the
heap each call to `_get_expanded_block_block_fast_path()`. Since this function
is called per-batch in a hot loop, consider making `row_ids` a member variable
(similar to `_block_fast_path_row`) and reusing it across calls with `clear()`
+ `reserve()` to avoid repeated heap allocations.
##########
be/src/exec/operator/table_function_operator.cpp:
##########
@@ -160,6 +167,154 @@ bool TableFunctionLocalState::_is_inner_and_empty() {
return false;
}
+bool TableFunctionLocalState::_can_use_block_fast_path() const {
+ auto& p = _parent->cast<TableFunctionOperatorX>();
+ // Fast path is only valid when:
+ // - only one table function exists
+ // - there is an active child row to expand
+ // - the child block is non-empty
+ // - the table function can expose nested/offsets via
prepare_block_fast_path()
+ return p._fn_num == 1 && _cur_child_offset != -1 && _child_block->rows() >
0 &&
+ _fns[0]->support_block_fast_path();
+}
+
+Status TableFunctionLocalState::_get_expanded_block_block_fast_path(
+ RuntimeState* state, std::vector<MutableColumnPtr>& columns) {
+ auto& p = _parent->cast<TableFunctionOperatorX>();
+ // Fast path for explode-like table functions:
+ // Instead of calling TableFunction::get_value() row-by-row, copy the
nested column range
+ // directly when the nested values in this output batch are contiguous in
memory.
+ if (!_block_fast_path_prepared) {
+ RETURN_IF_ERROR(
+ _fns[0]->prepare_block_fast_path(_child_block.get(), state,
&_block_fast_path_ctx));
+ _block_fast_path_prepared = true;
+ _block_fast_path_row = _cur_child_offset;
+ _block_fast_path_in_row_offset = 0;
+ }
+
+ const auto remaining_capacity =
+ state->batch_size() -
cast_set<int>(columns[p._child_slots.size()]->size());
+ if (remaining_capacity <= 0) {
+ return Status::OK();
+ }
+
+ if (_block_fast_path_ctx.offsets_ptr == nullptr ||
+ _block_fast_path_ctx.nested_col.get() == nullptr) {
+ return Status::InternalError("block fast path context is invalid");
+ }
+
+ const auto& offsets = *_block_fast_path_ctx.offsets_ptr;
+ const auto child_rows = cast_set<int64_t>(offsets.size());
+ if (child_rows != cast_set<int64_t>(_child_block->rows())) {
+ return Status::InternalError("block fast path offsets size mismatch");
+ }
+
+ std::vector<uint32_t> row_ids;
+ row_ids.reserve(remaining_capacity);
+ uint64_t first_nested_idx = 0;
+ uint64_t expected_next_nested_idx = 0;
+
+ int64_t child_row = _block_fast_path_row;
+ uint64_t in_row_offset = _block_fast_path_in_row_offset;
+ int produced_rows = 0;
+
+ while (produced_rows < remaining_capacity && child_row < child_rows) {
+ if (_block_fast_path_ctx.array_nullmap_data &&
+ _block_fast_path_ctx.array_nullmap_data[child_row]) {
+ // NULL array row: skip it here. Slow path will handle output
semantics if needed.
+ child_row++;
+ in_row_offset = 0;
+ continue;
+ }
+
+ const uint64_t prev_off = child_row == 0 ? 0 : offsets[child_row - 1];
+ const uint64_t cur_off = offsets[child_row];
+ const uint64_t nested_len = cur_off - prev_off;
+
+ if (in_row_offset >= nested_len) {
+ child_row++;
+ in_row_offset = 0;
+ continue;
+ }
+
+ const uint64_t remaining_in_row = nested_len - in_row_offset;
+ const int take_count =
+ std::min<int>(remaining_capacity - produced_rows,
cast_set<int>(remaining_in_row));
+ const uint64_t nested_start = prev_off + in_row_offset;
+
+ DCHECK_LE(nested_start + take_count, cur_off);
+ DCHECK_LE(nested_start + take_count,
_block_fast_path_ctx.nested_col->size());
+
+ if (produced_rows == 0) {
+ first_nested_idx = nested_start;
+ expected_next_nested_idx = nested_start;
+ } else if (nested_start != expected_next_nested_idx) {
+ // This fast path requires a single contiguous nested range to
avoid per-row scatter.
+ return Status::NotSupported("block fast path requires contiguous
nested range");
+ }
+
+ // Map each produced output row back to its source child row for
copying non-table-function
+ // columns via insert_indices_from().
+ for (int j = 0; j < take_count; ++j) {
+ row_ids.push_back(cast_set<uint32_t>(child_row));
Review Comment:
[Performance, Minor] This per-element `push_back` loop to build `row_ids`
can be replaced with a more efficient bulk fill:
```cpp
row_ids.resize(produced_rows + take_count);
std::fill(row_ids.begin() + produced_rows, row_ids.begin() + produced_rows +
take_count,
cast_set<uint32_t>(child_row));
```
Or even simpler, use `insert` with a count:
```cpp
row_ids.insert(row_ids.end(), take_count, cast_set<uint32_t>(child_row));
```
This avoids the per-iteration overhead of bounds checking and potential
reallocation inside the loop, especially when `take_count` can be large (entire
array in one go).
##########
be/src/exprs/function/array/function_array_utils.cpp:
##########
@@ -59,9 +59,18 @@ bool extract_column_array_info(const IColumn& src,
ColumnArrayExecutionData& dat
data.nested_type->get_primitive_type() != PrimitiveType::TYPE_VARIANT)
{
// set variant root column/type to from column/type
auto variant = ColumnVariant::create(true /*always nullable*/);
- auto nullable_nested_type = make_nullable(data.nested_type);
- auto nullable_col = make_nullable(data.nested_col);
- variant->create_root(nullable_nested_type,
std::move(*nullable_col).mutate());
+ auto root_col = data.nested_col->assume_mutable();
+ if (data.nested_type->is_nullable() && !root_col->is_nullable()) {
Review Comment:
[Scope] This change fixes a pre-existing bug in the variant output path
(where `make_nullable()` would lose the original null map information), but it
appears unrelated to the block fast path optimization. Consider splitting this
into a separate commit/PR for clearer git history and easier bisection.
--
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]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]