blob: c0030f633eb5e86e0253476c93f009820e737a3b [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "codegen/llvm-codegen.h"
#include "exec/cte-consumer-node.h"
#include "exec/exec-node-util.h"
#include "exprs/scalar-expr-evaluator.h"
#include "exprs/scalar-expr.h"
#include "runtime/exec-env.h"
#include "runtime/fragment-state.h"
#include "runtime/local-exchanger.h"
#include "runtime/row-batch.h"
#include "runtime/runtime-filter-bank.h"
#include "runtime/runtime-filter.inline.h"
#include "runtime/runtime-filter.h"
#include "runtime/runtime-state.h"
#include "runtime/tuple.h"
#include "util/debug-util.h"
#include "util/pretty-printer.h"
#include "util/runtime-profile-counters.h"
#include "util/runtime-profile.h"
#include "common/names.h"
DECLARE_double(min_filter_reject_ratio);
namespace impala {
constexpr int BATCHES_PER_FILTER_SELECTIVITY_CHECK = 16;
Status CTEConsumerPlanNode::Init(const TPlanNode& tnode, FragmentState* state) {
RETURN_IF_ERROR(PlanNode::Init(tnode, state));
DCHECK(tnode_->__isset.cte_consumer);
const TCTEConsumer& cte_node = tnode_->cte_consumer;
DCHECK_EQ(row_descriptor_->tuple_descriptors().size(), 1);
tuple_desc_ = row_descriptor_->tuple_descriptors()[0];
DCHECK_EQ(cte_node.input_row_tuples.size(), cte_node.nullable_tuples.size());
RowDescriptor input_row_desc(
state->desc_tbl(), cte_node.input_row_tuples, cte_node.nullable_tuples);
RETURN_IF_ERROR(
ScalarExpr::Create(cte_node.result_exprs, input_row_desc, state, &input_exprs_));
DCHECK_EQ(input_exprs_.size(), tuple_desc_->slots().size());
// Set up runtime filter expressions, one per filter assigned to this node.
for (const TRuntimeFilterDesc& filter_desc : tnode.runtime_filters) {
auto it = filter_desc.planid_to_target_ndx.find(tnode.node_id);
DCHECK(it != filter_desc.planid_to_target_ndx.end());
const TRuntimeFilterTargetDesc& target = filter_desc.targets[it->second];
ScalarExpr* filter_expr;
RETURN_IF_ERROR(
ScalarExpr::Create(target.target_expr, *row_descriptor_, state, &filter_expr));
runtime_filter_exprs_.push_back(filter_expr);
}
is_passthrough_ = row_descriptor_->LayoutEquals(input_row_desc);
return Status::OK();
}
void CTEConsumerPlanNode::Close() {
ScalarExpr::Close(input_exprs_);
ScalarExpr::Close(runtime_filter_exprs_);
PlanNode::Close();
}
Status CTEConsumerPlanNode::CreateExecNode(RuntimeState* state, ExecNode** node) const {
ObjectPool* pool = state->obj_pool();
*node = pool->Add(new CTEConsumerNode(pool, *this, state->desc_tbl()));
return Status::OK();
}
void CTEConsumerPlanNode::Codegen(FragmentState* state) {
DCHECK(state->ShouldCodegen());
PlanNode::Codegen(state);
if (IsNodeCodegenDisabled()) return;
if (is_passthrough_) return;
LlvmCodeGen* codegen = state->codegen();
DCHECK(codegen != nullptr);
std::stringstream codegen_message;
llvm::Function* tuple_materialize_exprs_fn;
Status codegen_status = Tuple::CodegenMaterializeExprs(codegen, false, *tuple_desc_,
input_exprs_, true, &tuple_materialize_exprs_fn);
if (!codegen_status.ok()) {
// Codegen may fail in some corner cases. If this happens, abort codegen.
AddCodegenStatus(codegen_status, "Codegen failed");
return;
}
// Get a copy of the function. This function will be modified and added to the
// vector of functions.
llvm::Function* materialize_batch_fn =
codegen->GetFunction(IRFunction::CTE_MATERIALIZE_BATCH, true);
DCHECK(materialize_batch_fn != nullptr);
int replaced = codegen->ReplaceCallSites(materialize_batch_fn,
tuple_materialize_exprs_fn, Tuple::MATERIALIZE_EXPRS_SYMBOL);
DCHECK_REPLACE_COUNT(replaced, 1) << LlvmCodeGen::Print(materialize_batch_fn);
materialize_batch_fn = codegen->FinalizeFunction(materialize_batch_fn);
DCHECK(materialize_batch_fn != nullptr);
// Add the function to Jit and to the vector of codegened functions.
codegen->AddFunctionToJit(materialize_batch_fn, &codegend_materialize_batch_fn_);
}
CTEConsumerNode::CTEConsumerNode(
ObjectPool* pool, const CTEConsumerPlanNode& pnode, const DescriptorTbl& descs)
: ExecNode(pool, pnode, descs),
tuple_desc_(pnode.tuple_desc_),
input_exprs_(pnode.input_exprs_),
codegend_materialize_batch_fn_(pnode.codegend_materialize_batch_fn_),
is_passthrough_(pnode.is_passthrough_) { }
Status CTEConsumerNode::Prepare(RuntimeState* state) {
RETURN_IF_ERROR(ExecNode::Prepare(state));
if (!is_passthrough_) {
RETURN_IF_ERROR(ScalarExprEvaluator::Create(input_exprs_, state, pool_,
expr_perm_pool(), expr_results_pool(), &input_expr_evals_));
}
const CTEConsumerPlanNode& pnode =
static_cast<const CTEConsumerPlanNode&>(plan_node());
const std::vector<ScalarExpr*>& filter_exprs = pnode.runtime_filter_exprs_;
DCHECK_EQ(filter_exprs.size(), plan_node().tnode_->runtime_filters.size());
for (int i = 0; i < filter_exprs.size(); ++i) {
const TRuntimeFilterDesc& filter_desc =
plan_node().tnode_->runtime_filters[i];
filter_ctxs_.emplace_back();
FilterContext& filter_ctx = filter_ctxs_.back();
filter_ctx.filter = state->filter_bank()->RegisterConsumer(filter_desc);
string filter_profile_title =
Substitute("$0$1 ($2)", RuntimeProfile::PREFIX_FILTER, filter_desc.filter_id,
PrettyPrinter::Print(filter_ctx.filter->filter_size(), TUnit::BYTES));
RuntimeProfile* filter_profile =
RuntimeProfile::Create(state->obj_pool(), filter_profile_title, false);
runtime_profile_->AddChild(filter_profile);
filter_ctx.stats = state->obj_pool()->Add(new FilterStats(filter_profile));
RETURN_IF_ERROR(ScalarExprEvaluator::Create(*filter_exprs[i], state, pool_,
expr_perm_pool(), expr_results_pool(), &filter_ctx.expr_eval));
}
filter_stats_.resize(filter_ctxs_.size());
// Must match CTEProducerPlanNode::GetCTEName. CTEProducerPlanNode::Init registers CTEs
// before fragment instances begin execution, so the mapping must exist here.
if (auto it = state->instance_ctx().cte_consumer_to_producer_idx.find(id());
it != state->instance_ctx().cte_consumer_to_producer_idx.end()) {
name_ = plan_node().tnode_->cte_consumer.name + "_" + std::to_string(it->second);
} else {
// No CTE producer scheduled on this node for this consumer. Already logged in
// Scheduler::ComputeFragmentExecParams.
name_ = plan_node().tnode_->cte_consumer.name;
}
return Status::OK();
}
Status CTEConsumerNode::Open(RuntimeState* state) {
SCOPED_TIMER(runtime_profile()->total_time_counter());
ScopedOpenEventAdder ea(this);
RETURN_IF_ERROR(ExecNode::Open(state));
if (!buffer_pool_client()->is_registered()) {
RETURN_IF_ERROR(ClaimBufferReservation(state));
}
if (!is_passthrough_) {
RETURN_IF_ERROR(ScalarExprEvaluator::Open(input_expr_evals_, state));
}
for (FilterContext& ctx : filter_ctxs_) {
RETURN_IF_ERROR(ctx.expr_eval->Open(state));
}
DCHECK_EQ(nullptr, exchanger_);
VLOG_QUERY << "Finding CTE exchange " << name_ << " in instance "
<< state->instance_ctx().per_fragment_instance_idx << " of " << label();
exchanger_ = state->query_state()->GetExchanger(name_);
if (exchanger_ != nullptr) {
consumer_index_ = exchanger_->Open();
} else {
VLOG_QUERY << "No CTE exchanger present for CTE consumer: " << name_;
}
return Status::OK();
}
Status CTEConsumerNode::GetNext(
RuntimeState* state, RowBatch* output_batch, bool* eos) {
SCOPED_TIMER(runtime_profile()->total_time_counter());
ScopedGetNextEventAdder ea(this, eos);
RETURN_IF_ERROR(ExecDebugAction(TExecNodePhase::GETNEXT, state));
RETURN_IF_CANCELLED(state);
RETURN_IF_ERROR(QueryMaintenance(state));
if (exchanger_ == nullptr) {
*eos = true;
return Status::OK();
}
// Fetch rows from LocalExchanger
RowBatch* input_batch = exchanger_->Pull(consumer_index_, eos);
if (input_batch == nullptr) {
// Pull only returns nullptr when eos_ is set and no batches remain.
DCHECK(*eos);
return Status::OK();
}
VLOG_PROGRESS << "Pulled " << input_batch->num_rows() << " rows from CTE exchange "
<< name_ << " in instance " << state->instance_ctx().per_fragment_instance_idx
<< " of " << label();
if (is_passthrough_) {
int rows_to_copy = input_batch->num_rows();
if (rows_to_copy > 0) {
DCHECK_LE(rows_to_copy, output_batch->capacity() - output_batch->num_rows())
<< "Output batch capacity: " << output_batch->capacity()
<< ", current rows: " << output_batch->num_rows()
<< ", input batch rows: " << input_batch->num_rows();
int dst_offset = output_batch->AddRows(rows_to_copy);
output_batch->CopyRows(input_batch, rows_to_copy, 0, dst_offset);
output_batch->CommitRows(rows_to_copy);
// Ensure blocking operators make a deep copy of data if they need to retain it.
// Heap memory is re-used from input_batch; if this is the last reference to the
// Cell in LocalExchanger, it will be freed on the next GetNext() call.
output_batch->MarkNeedsDeepCopy();
}
} else {
// Copy input_batch to output_batch while translating slots.
int64_t tuple_buf_size;
uint8_t* tuple_buf;
RETURN_IF_ERROR(output_batch->ResizeAndAllocateTupleBuffer(
state, &tuple_buf_size, &tuple_buf));
memset(tuple_buf, 0, tuple_buf_size);
CTEConsumerPlanNode::MaterializeBatchFn fn = codegend_materialize_batch_fn_.load();
if (fn == nullptr) {
MaterializeBatch(input_batch, output_batch, &tuple_buf);
} else {
fn(this, input_batch, output_batch, &tuple_buf);
}
}
if (!filter_ctxs_.empty()) {
if (!filters_waited_) {
filters_waited_ = true;
WaitForRuntimeFilters(state, filter_ctxs_);
}
FilterRowBatch(output_batch);
}
CheckLimitAndTruncateRowBatchIfNeeded(output_batch, eos);
COUNTER_SET(rows_returned_counter_, rows_returned());
return Status::OK();
}
Status CTEConsumerNode::Reset(RuntimeState* state, RowBatch* row_batch) {
// Reset() is not supported.
const char* msg = "Internal error: CTE consumer nodes should not appear in subplans.";
DCHECK(false) << msg;
return Status(msg);
}
void CTEConsumerNode::Close(RuntimeState* state) {
if (is_closed()) return;
FinalizeFilters(state);
if (exchanger_) {
VLOG_QUERY << "Closing consumer of CTE exchange " << name_ << " in instance "
<< state->instance_ctx().per_fragment_instance_idx << " of " << label();
exchanger_->CloseConsumer(consumer_index_);
}
if (!is_passthrough_) {
ScalarExprEvaluator::Close(input_expr_evals_, state);
}
ExecNode::Close(state);
}
void CTEConsumerNode::DebugString(int indentation_level, stringstream* out) const {
*out << string(indentation_level * 2, ' ') << "CTEConsumerNode(" << name_;
ExecNode::DebugString(indentation_level, out);
*out << ")";
}
bool CTEConsumerNode::EvalRuntimeFilters(
TupleRow* row, vector<LocalFilterContext>& local_filter_ctxs) noexcept {
for (LocalFilterContext& local_filter_ctx : local_filter_ctxs) {
LocalFilterStats& stats = local_filter_ctx.stats;
++stats.total_possible;
if (!stats.enabled_for_row || !local_filter_ctx.filter_ctx.filter->HasFilter()) {
continue;
}
++stats.considered;
if (!local_filter_ctx.filter_ctx.Eval(row)) {
++stats.rejected;
return false;
}
}
return true;
}
void CTEConsumerNode::FilterRowBatch(RowBatch* batch) noexcept {
int num_rows = batch->num_rows();
vector<LocalFilterContext> local_filter_ctxs;
local_filter_ctxs.reserve(filter_ctxs_.size());
for (int i = 0; i < filter_ctxs_.size(); ++i) {
local_filter_ctxs.emplace_back(filter_ctxs_[i], filter_stats_[i]);
}
int out_idx = 0;
for (int i = 0; i < num_rows; ++i) {
TupleRow* row = batch->GetRow(i);
if (EvalRuntimeFilters(row, local_filter_ctxs)) {
if (out_idx != i) batch->CopyRows(out_idx, i, 1);
++out_idx;
}
}
batch->set_num_rows(out_idx);
++row_batches_since_filter_check_;
if (row_batches_since_filter_check_ == BATCHES_PER_FILTER_SELECTIVITY_CHECK) {
CheckFiltersEffectiveness();
row_batches_since_filter_check_ = 0;
}
}
void CTEConsumerNode::CheckFiltersEffectiveness() noexcept {
for (int i = 0; i < filter_stats_.size(); ++i) {
LocalFilterStats& stats = filter_stats_[i];
const RuntimeFilter* filter = filter_ctxs_[i].filter;
double reject_ratio = stats.rejected / static_cast<double>(stats.considered);
if (filter->AlwaysTrue() || reject_ratio < FLAGS_min_filter_reject_ratio) {
stats.enabled_for_row = false;
}
}
}
void CTEConsumerNode::FinalizeFilters(RuntimeState* state) noexcept {
for (int i = 0; i < filter_stats_.size(); ++i) {
const LocalFilterStats& stats = filter_stats_[i];
filter_ctxs_[i].stats->IncrCounters(FilterStats::ROWS_KEY,
stats.total_possible, stats.considered, stats.rejected);
}
for (const FilterContext& ctx : filter_ctxs_) {
if (ctx.stats != nullptr) {
if (ctx.stats->HasRejectedRows()) {
effective_filter_ids_.push_back(ctx.filter->id());
}
ctx.expr_eval->Close(state);
}
}
}
}