blob: 0ce8fd819f6e692c9cdf8edaf3897dc132577e92 [file] [log] [blame]
/**********************************************************************
// @@@ START COPYRIGHT @@@
//
// 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.
//
// @@@ END COPYRIGHT @@@
/**********************************************************************/
/* -*-C++-*-
**************************************************************************
*
* File: CostMethod.C
* Description: Optimizer cost estimation interface object
* Created: 3/97
* Language: C++
*
* Purpose: Simple Cost Vector Reduction
*
*
*
**************************************************************************
*/
#include "GroupAttr.h"
#include "AllRelExpr.h"
#include "RelPackedRows.h"
#include "RelSequence.h"
#include "RelSample.h"
#include "AllItemExpr.h"
#include "ItemSample.h"
#include "opt.h"
#include "EstLogProp.h"
#include "DefaultConstants.h"
#include "ItemOther.h"
#include "ScanOptimizer.h"
#include "NAFileSet.h"
#include "SchemaDB.h"
#include "opt_error.h"
#include "CostMethod.h"
#include "Cost.h"
#include "NodeMap.h"
#include <math.h>
#include "OptimizerSimulator.h"
#include "CmpStatement.h"
//THREAD_P CostMethod* CostMethod::head_ = NULL;
#ifndef NDEBUG
static THREAD_P FILE* pfp = NULL;
#endif // NDEBUG
//<pb>
// A function having an external linkage to allow display() to
// be called. This is a workaround for bugs/missing
// functionality in ObjectCenter that cause display() to become
// an undefined symbol.
void displayCostMethod(const CostMethod& pf)
{
pf.display();
}
void displayCostMethod(const CostMethod* pf)
{
if (pf)
pf->display();
}
//<pb>
//************************************************
// Global functions related to unary cost roll-up.
//************************************************
//==============================================================================
// Combine and roll up cost of a non-blocking parent and its single child.
//
// Input:
// parentOnly -- Cost of parent independent of its child.
//
// childRollUp -- Combined cost of child and all its dependents.
//
// rpp -- Parent's required physical properties necessary for
// performing a blocking additon.
//
// Output:
// none
//
// Return:
// Rolled up cost.
//
//==============================================================================
Cost*
rollUpUnaryNonBlocking(const Cost& parentOnly,
const Cost& childRollUp,
const ReqdPhysicalProperty* const rpp)
{
//-----------------------
// Create an empty cost.
//-----------------------
Cost* rollUp = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// For total cost, simply accumulate all resource usage with simple vector
// addition.
//-------------------------------------------------------------------------
rollUp->totalCost() = parentOnly.getTotalCost() + childRollUp.getTotalCost();
//------------------------------------------------------------------------
// For current process first row cost, use blocking addition since the
// parent can't proceed until it receives at least one row from its child.
//------------------------------------------------------------------------
rollUp->cpfr() = blockingAdd(parentOnly.getCpfr(),
childRollUp.getCpfr(),
rpp);
//------------------------------------------------------------------------
// For a non-blocking roll-up, all of the child's efforts except its work
// producing its first row overlap with the work of the parent. Thus, the
// formula below involves an overlapped addition of the parents last row
// cost and the child's cumulative last row cost less its first row cost.
// The child's first row cost is added back using simple vector addition.
//------------------------------------------------------------------------
rollUp->cplr() = overlapAddUnary(parentOnly.getCplr(),
childRollUp.getCplr() - childRollUp.getCpfr())
+ childRollUp.getCpfr();
//--------------------------------------------------------------------
// Ensure that no component of rolled up first row vector exceeds the
// corresponding component of rolled up last row vector.
//--------------------------------------------------------------------
rollUp->cpfr().enforceUpperBound(rollUp->cplr());
//-----------------------------------------------------------------------
// For a non-blocking roll-up, a parent simply reports whatever blocking
// costs and overlapped process costs its child reported. Blocking costs
// are normalized to the parent's number of probes.
//-----------------------------------------------------------------------
const CostScalar & parentNumProbes = parentOnly.getCplr().getNumProbes();
rollUp->cpbc1() =
childRollUp.getCpbc1().getNormalizedVersion(parentNumProbes);
rollUp->cpbcTotal() =
childRollUp.getCpbcTotal().getNormalizedVersion(parentNumProbes);
//jo rollUp->opfr() = childRollUp.getOpfr();
//jo rollUp->oplr() = childRollUp.getOplr();
return rollUp;
} // rollUpUnaryNonBlocking
//<pb>
//==============================================================================
// Combine and roll up cost of a blocking parent and its single child.
//
// Input:
// parentOnly -- Cost of parent independent of its child.
//
// childRollUp -- Combined cost of child and all its dependents.
//
// rpp -- Parent's required physical properties necessary for
// performing a blocking additon.
//
// Output:
// none
//
// Return:
// Rolled up cost.
//
//==============================================================================
Cost*
rollUpUnaryBlocking(const Cost& parentOnly,
const Cost& childRollUp,
const ReqdPhysicalProperty* const rpp)
{
//-----------------------
// Create an empty cost.
//-----------------------
Cost* rollUp = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// For total cost, simply accumulate all resource usage with simple vector
// addition.
//-------------------------------------------------------------------------
rollUp->totalCost() = parentOnly.getTotalCost() + childRollUp.getTotalCost();
//----------------------------------------------------------------------
// For a blocking parent, the rolled-up first row and last row costs
// become the parent's first row and last row costs respectively.
//----------------------------------------------------------------------
rollUp->cpfr() = parentOnly.getCpfr();
rollUp->cplr() = parentOnly.getCplr();
//---------------------------------------------------------------------
// Compute number of probes associated with parent's preliminary cost.
//---------------------------------------------------------------------
const CostScalar & parentNumProbes = parentOnly.getCpbc1().getNumProbes();
//------------------------------------------------------------------------
// If parent is first blocking operator encountered, set the roll-up
// first blocking cost to the sum of the parent's first blocking cost and
// the child roll-up last row cost. (Remember to convert the child's last
// row cost from an total cost to an average cost by dividing it by the
// parent's number of probes.)
//
//------------------------------------------------------------------------
if ( childRollUp.getCpbc1().isZeroVectorWithProbes() )
{
rollUp->cpbc1() = overlapAddUnary(parentOnly.getCpbc1(),
childRollUp.getCplr() / parentNumProbes);
}
else
{
//--------------------------------------------------------------------
// Parent not first blocking operator. Roll up first blocking
// cost reported by child but normalized to parent's number of probes.
//--------------------------------------------------------------------
rollUp->cpbc1() =
childRollUp.getCpbc1().getNormalizedVersion(parentNumProbes);
}
//----------------------------------------------------------------------
// Roll up total blocking cost as the sum of the parents blocking
// cost, the child's last row cost and the child's total blocking cost.
// Remember to convert the child's last row cost from an total cost to
// an average cost by dividing it by the number of probes.
//
// The parent's blocking activity overlaps with the child's last row
// activity, so these are added using overlapped addition. The parent's
// blocking activity, however, must wait for the child's blocking
// activity, so the child's total blocking cost is added using
// blocking addition.
//
//----------------------------------------------------------------------
rollUp->cpbcTotal() =
blockingAdd(
overlapAddUnary(parentOnly.getCpbc1(),
childRollUp.getCplr() / parentNumProbes),
childRollUp.getCpbcTotal().getNormalizedVersion(parentNumProbes),
rpp);
//-----------------------------------------------------------------
// For standard blocking roll-up, a parent simply reports whatever
// overlapped process costs its child reported.
//-----------------------------------------------------------------
//jo rollUp->opfr() = childRollUp.getOpfr();
//jo rollUp->oplr() = childRollUp.getOplr();
return rollUp;
} // rollUpUnaryBlocking
//<pb>
/**********************************************************************/
/* */
/* CostMethod */
/* */
/**********************************************************************/
/* Base class for all CostMethod objects */
/**********************************************************************/
CostMethod::CostMethod( const char* className ) : className_( className )
{
if ( CURRENTSTMT ) {
nextCostMethod_ = CURRENTSTMT->getCostMethodHead();
CURRENTSTMT->setCostMethodHead(this);
} else {
nextCostMethod_ = NULL;
}
}
// Print
void
CostMethod::print( FILE* ofd
, const char* indent
, const char* title
) const
{
BUMP_INDENT(indent);
fprintf(ofd,"%s ",NEW_INDENT);
if (title)
fprintf(ofd,"%s ",title);
if (className_)
fprintf(ofd,"%s ",className_);
fprintf(ofd,"\n ");
} // CostMethod::print()
void
CostMethod::display() const { print(); }
// -----------------------------------------------------------------------
// CostMethod::cleanUpAllCostMethods() is used to reset the SharedPtrs in
// the CostMethod derived classes when a longjmp occurs. This is necessary
// because the CostMethod objects may be pointing to an old statement
// heap after the longjmp(). The next statement will cause problems if
// the SharedPtrs are still pointing to the old statement heap. This
// function may also clean up other problems that may exist in the
// CostMethod objects when a longjmp occurs.
// -----------------------------------------------------------------------
void
CostMethod::cleanUpAllCostMethods()
{
if ( !CURRENTSTMT )
return;
for (CostMethod *cm = CURRENTSTMT->getCostMethodHead();
cm != NULL; cm = cm->nextCostMethod_)
cm->cleanUp();
}
// -----------------------------------------------------------------------
// CostMethod::generateZeroCostObject()
// Generate a zero cost object out of the information already cached.
// -----------------------------------------------------------------------
Cost* CostMethod::generateZeroCostObject()
{
// A zero cost vector.
SimpleCostVector cv(
csZero, // CPU TIME
csZero, // IO TIME
csZero, // MSG TIME
csZero, // idle time
noOfProbesPerStream_ // num probes
);
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
// Synthesize the "zero" cost object.
return new STMTHEAP Cost( &cv, &cv, NULL, cpuCount, fragmentsPerCPU );
}
// return true iff we are under a nested join
NABoolean
CostMethod::isUnderNestedJoin( RelExpr* op, const Context* myContext )
{
// begin code extracted from CostMethod::cacheParameters ---------------
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( op != NULL );
CMPASSERT( myContext != NULL );
op_ = op;
context_ = myContext;
inLogProp_ = context_->getInputLogProp();
CMPASSERT(inLogProp_ != NULL);
noOfProbes_ = ( inLogProp_->getResultCardinality() ).minCsOne();
ga_ = op_->getGroupAttr();
CMPASSERT(ga_ != NULL);
// Check if the operator has outer col references (under a NestedJoin).
const ValueIdSet& inVis = ga_->getCharacteristicInputs();
ValueIdSet outerRef;
inVis.getOuterReferences(outerRef);
hasOuterReferences_ = (NOT outerRef.isEmpty());
// ---------------------------------------------------------------------
// Is this operator on the right leg of a NJ? Note that we couldn't tell
// for sure. This is just a most-of-the-time-true heuristic. In a very
// rare case, an operator on the right leg of a NJ can have no outer
// references and takes just one probe. One example is:
//
// NJ Table A can produce exactly one row and
// / \ Table B can have no outer references if
// A Mat-(A.x=B.x) all the predicates are evaluated at Mat.
// |
// B
// ---------------------------------------------------------------------
isUnderNestedJoin_ = ( hasOuterReferences_ OR
noOfProbes_.isGreaterThanOne() /* > csOne */ );
// end code extracted from CostMethod::cacheParameters -----------------
return isUnderNestedJoin_;
}
//<pb>
// -----------------------------------------------------------------------
// CostMethod::cacheParameters()
// -----------------------------------------------------------------------
void CostMethod::cacheParameters(RelExpr* op, const Context* myContext)
{
(void) isUnderNestedJoin(op, myContext);
rpp_ = context_->getReqdPhysicalProperty();
if(rpp_)
partReq_ = rpp_->getPartitioningRequirement();
else
{
// Only a RELROOT may have a NULL rpp_.
CMPASSERT(op->getOperatorType() == REL_ROOT);
partReq_ = NULL;
}
partFunc_ = NULL;
myLogProp_ = ga_->outputLogProp(inLogProp_);
CMPASSERT(myLogProp_ != NULL);
myRowCount_ = ( myLogProp_->getResultCardinality() ).minCsOne();
// Determine if the operator is a big memory operator
if (context_->getPlan()->isBigMemoryOperator())
{
isBMO_ = TRUE;
memoryLimit_ = CURRSTMT_OPTDEFAULTS->getMemoryLimitPerCPU();
}
else
{
isBMO_ = FALSE;
memoryLimit_ = 0.0;
}
isMemoryLimitExceeded_ = FALSE;
// No of CPUs available on this system.
countOfAvailableCPUs_ = (rpp_ ? rpp_->getCountOfAvailableCPUs() : 1);
// Maximum count of pipelines allowed per CPU.
countOfPipelinesPerCPU_ = (rpp_ ? rpp_->getCountOfPipelines() : 1) /
countOfAvailableCPUs_;
// Initialization, just in case operator doesn't call estDegOfParallism().
countOfStreams_ = 1;
noOfProbesPerStream_ = ( noOfProbes_ ).minCsOne();
// ---------------------------------------------------------------------
// Support for recosting with the operator's synthesized physical prop
// when it becomes available when we're going up the tree.
// ---------------------------------------------------------------------
const PhysicalProperty* spp = context_->getPlan()->getPhysicalProperty();
if( spp != NULL )
{
// Set partFunc_ to the actual partitioning function.
partFunc_ = spp->getPartitioningFunction();
CMPASSERT( partFunc_ );
// This is the operator's actual degree of parallelism.
countOfAvailableCPUs_ = spp->getCurrentCountOfCPUs();
}
} // CostMethod::cacheParameters()
//<pb>
// -----------------------------------------------------------------------
// CostMethod::estimateDegreeOfParallelism().
//
// Generic code for deciding on the degree of parallelism this operator
// exhibits for the purpose of costing. Different operators should refine
// this implementation if it's not sufficient. It assumes parameters have
// been cached.
//
// This implementation computes two parameters:
// countOfStreams_ and noOfProbesPerStream_.
// -----------------------------------------------------------------------
void CostMethod::estimateDegreeOfParallelism()
{
if (partFunc_ != NULL)
{
countOfStreams_ = partFunc_->getCountOfPartitions();
ValueIdSet partKey = partFunc_->getPartitioningKey();
long randomFix = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_26);
if ( (partKey.entries() == 1) AND (randomFix != 0) )
{
// Get first key column.
ValueId myPartKeyCol;
partKey.getFirst(myPartKeyCol);
// is it a random number?
if (myPartKeyCol.getItemExpr()->getOperatorType() == ITM_RANDOMNUM)
{
CostScalar activeStreams = partFunc_->getActiveStreams();
(CostScalar)countOfStreams_ =
MINOF(activeStreams, (CostScalar)countOfStreams_);
}
}
}
// ---------------------------------------------------------------------
// Estimates are based on what's specified in rpp_ of an operator which
// is the only hint available when we are going down the query tree at
// optimization.
// ---------------------------------------------------------------------
else if(rpp_ != NULL)
{
if((partReq_ != NULL) AND
(partReq_->getCountOfPartitions() != ANY_NUMBER_OF_PARTITIONS))
{
countOfStreams_ = partReq_->getCountOfPartitions();
}
else
{
// must underestimate, since we are on our way down.
countOfStreams_ = rpp_->getCountOfPipelines();
}
}
else
// ---------------------------------------------------------------------
// No rpp specified. True only for RelRoot which runs in one stream.
// ---------------------------------------------------------------------
{
CMPASSERT(op_->getOperatorType() == REL_ROOT);
countOfStreams_ = 1;
}
// If this operator is on the right leg of a parallel nested join,
// then limit the countOfStreams_ by the number of probes, because
// if we have fewer probes than the number of streams, then some
// streams will be inactive.
if ((partReq_ != NULL) AND
partReq_->isRequirementReplicateNoBroadcast() )
{
CostScalar tempCountOfStreams= MINOF(CostScalar(countOfStreams_),
noOfProbes_.getCeiling());
countOfStreams_ = Lng32(tempCountOfStreams.value());
}
CMPASSERT(countOfStreams_ > 0);
// ---------------------------------------------------------------------
// The following code determines the no of probes an instance of this
// operator receives.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// We have two possible cases:
// 1. I am asked to execute serially, in which case countOfStreams = 1.
// 2. I am trying to execute in parallel and the probes are distributed
// over each parallel instance of mine.
//
// In both cases, we expect noOfProbes_ is just scaled down by
// a factor of countOfStreams_ to produce noOfProbesPerStream_.
//
// ---------------------------------------------------------------------
// Using the CQD INCORPORATE_SKEW_IN_COSTING we control the enhancement done for
// estimating number of probes per stream. If the code is activated
// number of probes per stream will be taken as cardinality of the
// busiest stream
// ---------------------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_ );
}
else
noOfProbesPerStream_ = ( noOfProbes_ / countOfStreams_ ).minCsOne();
} // CostMethod::estimateDegreeOfParallelism().
//<pb>
void CostMethod::determineCpuCountAndFragmentsPerCpu(
Lng32 & cpuCount,
Lng32 & fragmentsPerCpu )
{
//------------------------------------------------------------------
// Count of streams limits the no of cpu this operator executes on.
//------------------------------------------------------------------
cpuCount = ( countOfStreams_ < countOfAvailableCPUs_ ?
countOfStreams_ : countOfAvailableCPUs_ );
CMPASSERT( cpuCount >= 1 ); // sanity test
//-------------------------------------------------------------------
// Compute maximum number of fragments per cpu by taking the ceiling
// of the number of streams divided by the number of cpus.
//-------------------------------------------------------------------
fragmentsPerCpu = ( ( countOfStreams_ + ( cpuCount - 1 ) ) / cpuCount );
CMPASSERT( fragmentsPerCpu >= 1 ); // sanity test
}
// -----------------------------------------------------------------------
// CostMethod::cleanUp().
// -----------------------------------------------------------------------
inline void CostMethod::cleanUp()
{
// The EstLogPropSharedPtr values must be cleaned up. If they are left
// around at the end of the statement, the next statement that executes
// will probably cause memory corruption or crashes because the pointers
// will be pointing to the old statement heap.
inLogProp_ = 0;
myLogProp_ = 0;
}
// -----------------------------------------------------------------------
// CostMethod::computeOperatorCost()
// -----------------------------------------------------------------------
Cost*
CostMethod::computeOperatorCost(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
Cost* cost;
try {
cost = computeOperatorCostInternal(op, myContext, countOfStreams);
} catch(...) {
// cleanUp() must be called before this function is called again
// because wrong results may occur the next time computeOperatorCost()
// is called and because the SharedPtr objects must be set to zero.
// Failure to call cleanUp() will very likely cause problems.
cleanUp();
throw; // rethrow the exception
}
cleanUp();
return cost;
} // CostMethod::computeOperatorCost()
//<pb>
//==============================================================================
// Default implementation to produce an PartialPlan cost for a specified
// physical operator and all its known children. Both the PartialPlan cost and
// the known children cost get stored in a specified plan workspace
//
// Input:
// op -- specified physical operator.
//
// myContext -- context associated with specified physical operator
//
// pws -- plan workspace associated with specified physical operator.
//
// Output:
// none
//
// Return:
// none
//
//==============================================================================
void
CostMethod::computePartialPlanCost(const RelExpr* op,
PlanWorkSpace* pws,
const Context* myContext)
{
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( op != NULL );
CMPASSERT( pws != NULL );
CMPASSERT( myContext != NULL );
//----------------------------------------------------------------------
// PartialPlan costing only appropriate for operators which are neither
// leaves nor unary.
//----------------------------------------------------------------------
CMPASSERT( op->getArity() > 1 );
//------------------------------------------
// Extract latest plan from plan workspace.
//------------------------------------------
Lng32 planNumber = pws->getLatestPlan();
//---------------------------------------------------------------
// Extract parent operator's local cost from the plan workspace.
// This cost should contain result of computeOperatorCost().
//---------------------------------------------------------------
Cost* parentCost = pws->getOperatorCost();
//----------------------------------------
// Accumulate cost of all known children.
//----------------------------------------
Cost* knownChildrenCost = new STMTHEAP Cost();
for ( Lng32 childIdx = 0; childIdx < op->getArity(); childIdx++ )
{
//-----------------------------------------------------
// Get current child's context via the plan workspace.
//-----------------------------------------------------
Context* childContext = pws->getChildContext( childIdx, planNumber );
//----------------------------------------------
// See if current child has an optimal solution.
//----------------------------------------------
if ( childContext != NULL && childContext->hasOptimalSolution() )
{
//--------------------------------------------------------------------
// Current child has an optimal solution, so accumulate its cost into
// a cumulative cost of known children.
//--------------------------------------------------------------------
const Cost* childCost = childContext->getSolution()->getRollUpCost();
knownChildrenCost->mergeOtherChildCost( *childCost );
}
}
Cost* partialPlanCost = NULL;
//-----------------------------------------------------------------
// Produce PartialPlan cost by rolling up known children cost with
// parent's preliminary cost.
//-----------------------------------------------------------------
if (CmpCommon::getDefault(SIMPLE_COST_MODEL) == DF_ON)
partialPlanCost = scmRollUp( parentCost,
knownChildrenCost,
NULL,
myContext->getReqdPhysicalProperty() );
else
partialPlanCost = rollUp( parentCost,
knownChildrenCost,
myContext->getReqdPhysicalProperty() );
//---------------------------------------------------------------------------
// Save off both PartialPlan cost and known children cost in plan workspace.
//---------------------------------------------------------------------------
pws->setPartialPlanCost( partialPlanCost );
pws->setKnownChildrenCost( knownChildrenCost );
} // CostMethod::computePartialPlanCost()
//<pb>
//==============================================================================
// Default implementation to produce a final cumulative cost for an entire
// subtree rooted at a specified physical operator.
//
// Input:
// op -- specified physical operator.
//
// myContext -- context associated with specified physical operator
//
// pws -- plan workspace associated with specified physical operator.
//
// planNumber -- used to get appropriate child contexts.
//
// Output:
// none
//
// Return:
// Pointer to cumulative final cost.
//
//==============================================================================
Cost*
CostMethod::computePlanCost( RelExpr* op,
const Context* myContext,
const PlanWorkSpace* pws,
Lng32 planNumber
)
{
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( op != NULL );
CMPASSERT( myContext != NULL );
CMPASSERT( pws != NULL );
//--------------------------------------------------------------------------
// Grab parent's cost (independent of its children) directly from the plan
// work space. This cost should contain result of computeOperatorCost().
//--------------------------------------------------------------------------
// Need to cast constness away since getFinalOperatorCost cannot
// be made const
Cost* parentCost = ((PlanWorkSpace *)pws)->getFinalOperatorCost( planNumber );
//----------------------------------------------------------------------------
// For leaf nodes (i.e. those having no children), return a copy of parent's
// cost as the final cost.
//----------------------------------------------------------------------------
if ( op->getArity() == 0 )
{
return parentCost;
}
//--------------------------------------------------------
// Merge all children's costs in preparation for roll-up.
//--------------------------------------------------------
Cost mergedChildCost;
for ( Lng32 childIdx = 0; childIdx < op->getArity(); childIdx++ )
{
//------------------------------------------------------
// Get current child's context via our plan work space.
//------------------------------------------------------
Context* childContext = pws->getChildContext( childIdx, planNumber );
//------------------------------------------------------------------
// Make sure plans are already generated by the operator's children.
//------------------------------------------------------------------
if ( childContext == NULL )
{
ABORT("CostMethod::computePlanCost(): A child has a NULL context");
}
// Coverity flags this dereferencing null pointer childContext.
// This is a false positive, we fix it using annotation.
// coverity[var_deref_model]
if ( NOT childContext->hasOptimalSolution() )
{
ABORT("CostMethod::computePlanCost(): A child has no solution");
}
//---------------------------------------------
// Accumulate this child's cost into PlanCost.
//---------------------------------------------
mergedChildCost.mergeOtherChildCost(
*childContext->getSolution()->getRollUpCost() );
}
if(op->getOperatorType()==REL_SHORTCUT_GROUPBY && op->getFirstNRows() == 1)
{
mergedChildCost.cpfr() = mergedChildCost.getCpfr() * 0.8;
mergedChildCost.cplr() = mergedChildCost.getCpfr();
}
Cost* planCost = rollUp( parentCost
, &mergedChildCost
, myContext->getReqdPhysicalProperty()
);
delete parentCost;
return planCost;
} // CostMethod::computePlanCost()
//<pb>
//==============================================================================
// Obtain copies of costs for a physical binary operator's two children.
//
// Input:
// op -- specified physical binary operator.
//
// myContext -- context associated with specified physical binary
// operator.
//
// pws -- plan work space associated with specified physical
// binary operator.
//
// planNumber -- used to get appropriate child contexts.
//
// Output:
// leftChildCost -- pointer to a copy of left child's cost.
//
// rightChildCost -- pointer to a copy of right child's cost.
//
// Return:
// none.
//
//==============================================================================
void
CostMethod::getChildCostsForBinaryOp( RelExpr* op
, const Context* myContext
, const PlanWorkSpace* pws
, Lng32 planNumber
, CostPtr& leftChildCost
, CostPtr& rightChildCost
)
{
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( op != NULL );
CMPASSERT( myContext != NULL );
CMPASSERT( pws != NULL );
//--------------------------------------------------------------------
// By definition, a binary operator should have exactly two children.
//--------------------------------------------------------------------
CMPASSERT( op->getArity() == 2 );
//-----------------------------------------------------------------
// Obtain left child's cost after verifying that left child has an
// associated context and optimal soloution.
//-----------------------------------------------------------------
Context* childContext = pws->getChildContext( 0, planNumber );
if ( childContext == NULL )
{
ABORT(
"CostMethod::getChildCostsForBinaryOp(): Left child has a NULL context"
);
}
// Coverity flags this dereferencing null pointer childContext.
// This is a false positive, we fix it using annotation.
// coverity[var_deref_model]
if ( NOT childContext->hasOptimalSolution() )
{
ABORT(
"CostMethod::getChildCostsForBinaryOp(): Left child has no solution"
);
}
leftChildCost =
new STMTHEAP Cost( *childContext->getSolution()->getRollUpCost() );
//-------------------------------------------------------------------
// Obtain right child's cost after verifying that right child has an
// associated context and optimal soloution.
//-------------------------------------------------------------------
childContext = pws->getChildContext( 1, planNumber );
if ( childContext == NULL )
{
ABORT(
"CostMethod::getChildCostsForBinaryOp(): Right child has a NULL context"
);
}
// Coverity flags this dereferencing null pointer childContext.
// This is a false positive, we fix it using annotation.
// coverity[var_deref_model]
if ( NOT childContext->hasOptimalSolution() )
{
ABORT(
"CostMethod::getChildCostsForBinaryOp(): Right child has no solution"
);
}
rightChildCost =
new STMTHEAP Cost( *childContext->getSolution()->getRollUpCost() );
}
void
CostMethod::getChildCostForUnaryOp( RelExpr* op
, const Context* myContext
, const PlanWorkSpace* pws
, Lng32 planNumber
, CostPtr& childCost
)
{
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( op != NULL );
CMPASSERT( myContext != NULL );
CMPASSERT( pws != NULL );
//--------------------------------------------------------------------
// By definition, a binary operator should have exactly two children.
//--------------------------------------------------------------------
CMPASSERT( op->getArity() == 1 );
//-----------------------------------------------------------------
// Obtain left child's cost after verifying that left child has an
// associated context and optimal soloution.
//-----------------------------------------------------------------
Context* childContext = pws->getChildContext( 0, planNumber );
if ( childContext == NULL )
{
ABORT(
"CostMethod::getChildCostForUnaryOp(): child has a NULL context"
);
}
// Coverity flags this dereferencing null pointer childContext.
// This is a false positive, we fix it using annotation.
// coverity[var_deref_model]
if ( NOT childContext->hasOptimalSolution() )
{
ABORT(
"CostMethod::getChildCostForUnaryOp(): child has no solution"
);
}
childCost =
new STMTHEAP Cost( *childContext->getSolution()->getRollUpCost() );
}
//<pb>
//==============================================================================
// Roll up a child's cumulative cost into its parent's cost.
//
// Input:
// parentCost -- Cost of parent independent of its child.
//
// childCost -- Combined cost of child and all its dependents.
//
// rpp -- Parent's required physical properties needed by lower level
// roll-up routines.
//
// Output:
// none
//
// Return:
// Rolled up cost.
//
//==============================================================================
Cost*
CostMethod::rollUp( Cost* const parentCost
, Cost* const childCost
, const ReqdPhysicalProperty* const rpp
)
{
//------------------------------------------------------------------
// If current operator is non-blocking, use a non-blocking roll up.
//------------------------------------------------------------------
if (parentCost->getCpbc1().isZeroVectorWithProbes())
{
return rollUpUnaryNonBlocking(*parentCost, *childCost, rpp);
}
else
{
//-------------------------------------------------------
// Current operator is blocking; use a blocking roll up.
//-------------------------------------------------------
return rollUpUnaryBlocking(*parentCost, *childCost, rpp);
}
} // CostMethod::rollUp()
//<pb>
//==============================================================================
// Default implementation to produce a final cumulative cost for an entire
// subtree rooted at a specified binary physical operator.
//
// Input:
// op -- specified binary physical operator.
//
// myContext -- context associated with specified physical operator
//
// pws -- plan work space associated with specified physical operator.
//
// planNumber -- used to get appropriate child contexts.
//
// Output:
// none
//
// Return:
// Pointer to cumulative final cost.
//
//==============================================================================
Cost*
CostMethod::rollUpForBinaryOp( RelExpr* op
, const Context* myContext
, const PlanWorkSpace* pws
, Lng32 planNumber
)
{
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( op != NULL );
CMPASSERT( myContext != NULL );
CMPASSERT( pws != NULL );
//--------------------------------------------------------------------------
// Get cumulative costs associated with each child of this binary operator.
//--------------------------------------------------------------------------
CostPtr leftChildCost;
CostPtr rightChildCost;
getChildCostsForBinaryOp( op
, myContext
, pws
, planNumber
, leftChildCost
, rightChildCost);
//--------------------------------------------------------------------------
// Merging of children's costs depend on whether or not any of the children
// have blocking operators.
//--------------------------------------------------------------------------
Cost* mergedChildCost;
const ReqdPhysicalProperty* rpp = myContext->getReqdPhysicalProperty();
if ( leftChildCost->getCpbcTotal().isZeroVectorWithProbes() )
{
if ( rightChildCost->getCpbcTotal().isZeroVectorWithProbes() )
{
//-------------------------------------------------------
// Neither child has a blocking operator in its subtree.
//-------------------------------------------------------
mergedChildCost = mergeNoLegsBlocking( leftChildCost,
rightChildCost,
rpp
);
}
else
{
//----------------------------------------------------------
// Only right child has a blocking operator in its subtree.
// Convert left child to blocking and proceed as if both
// children were blocking.
//----------------------------------------------------------
Cost* blockingLeftChildCost = convertToBlocking( leftChildCost );
mergedChildCost = mergeBothLegsBlocking( blockingLeftChildCost,
rightChildCost,
rpp
);
delete blockingLeftChildCost;
}
}
else
{
if ( rightChildCost->getCpbcTotal().isZeroVectorWithProbes() )
{
//---------------------------------------------------------
// Only left child has a blocking operator in its subtree.
// Convert right child to blocking and proceed as if both
// children were blocking.
//---------------------------------------------------------
Cost* blockingRightChildCost = convertToBlocking( rightChildCost );
mergedChildCost = mergeBothLegsBlocking( leftChildCost,
blockingRightChildCost,
rpp
);
delete blockingRightChildCost;
}
else
{
//----------------------------------------------------------
// Both children have blocking operators in their subtrees.
//----------------------------------------------------------
mergedChildCost = mergeBothLegsBlocking( leftChildCost,
rightChildCost,
rpp
);
}
}
//-----------------------------------------------------------------------
// Child costs have been merged at this point, so delete local copies of
// those costs.
//-----------------------------------------------------------------------
delete leftChildCost;
delete rightChildCost;
//----------------------------------------------------------------------
// Get addressability to parent cost in plan workspace and roll this up
// with the recently calculated merged children cost.
//----------------------------------------------------------------------
Cost* parentCost = ((PlanWorkSpace *)pws)->getFinalOperatorCost( planNumber );
Cost* rollUpCost = rollUp( parentCost, mergedChildCost, rpp );
//----------------------------------------------------------------------
// Parent cost and local copy of merged child cost have been rolled up
// at this point, so delete them.
//----------------------------------------------------------------------
delete parentCost;
delete mergedChildCost;
//--------------------------------------------
// Return previously calculated roll-up cost.
//--------------------------------------------
return rollUpCost;
} // CostMethod::rollUpForBinaryOp
//<pb>
//==============================================================================
// This merge routine should never be called for CostMethod base class.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// rpp -- Parent's required physical properties.
//
// Output:
// none
//
// Return:
// Always NULL.
//
//==============================================================================
Cost*
CostMethod::mergeNoLegsBlocking( const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp)
{
ABORT("CostMethod::mergeNoLegsBlocking");
return NULL;
} // CostMethod::mergeNoLegsBlocking
//<pb>
//==============================================================================
// Convert a non-blocking cost into a blocking cost using a canonical
// transformation which moves the first row vector to both blocking vectors and
// then resets the first row vector.
//
// Note: callers must delete returned cost object.
//
// Input:
// nonBlockingCost -- pointer to cumulative non-blocking cost.
//
// Output:
// none
//
// Return:
// Pointer to converted blocking cost.
//
//==============================================================================
Cost*
CostMethod::convertToBlocking( const CostPtr nonBlockingCost )
{
//------------------------------------------------------
// Verify that non-blocking cost is indeed non-blocking.
//------------------------------------------------------
CMPASSERT( nonBlockingCost != NULL );
CMPASSERT( nonBlockingCost->getCpbc1().isZeroVectorWithProbes() );
CMPASSERT( nonBlockingCost->getCpbcTotal().isZeroVectorWithProbes() );
//----------------------------------------------------------------------------
// Convert cost vectors of non-blocking child to look like a blocking vector.
// Move first row cost to both blocking vectors and subtract first row cost
// from last row cost.
//----------------------------------------------------------------------------
Cost* blockingCost = new STMTHEAP Cost( *nonBlockingCost );
blockingCost->cplr() -= blockingCost->cpfr();
const CostScalar & numProbes = blockingCost->getCplr().getNumProbes();
blockingCost->cpbcTotal() = blockingCost->cpfr();
blockingCost->cpbcTotal().setNumProbes( numProbes );
blockingCost->cpbc1() = blockingCost->cpfr();
blockingCost->cpbc1().setNumProbes( numProbes );
//---------------------------------------------------------------------
// Zero out first row cost vector yet preserving its number of probes.
//---------------------------------------------------------------------
blockingCost->cpfr().reset();
blockingCost->cpfr().setNumProbes( csOne );
return blockingCost;
} // CostMethod::convertToBlocking
//<pb>
//==============================================================================
// This merge routine should never be called for CostMethod base class.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// rpp -- Parent's required physical properties.
//
// Output:
// none
//
// Return:
// Always NULL.
//
//==============================================================================
Cost*
CostMethod::mergeBothLegsBlocking( const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp)
{
ABORT("CostMethod::mergeBothLegsBlocking");
return NULL;
} // CostMethod::mergeBothLegsBlocking
//<pb>
/**********************************************************************/
/* */
/* CostMethodExchange */
/* */
/**********************************************************************/
//<pb>
//==============================================================================
// Compute operator cost for a specified Exchange operator.
//
// Input:
// op -- pointer to specified Exchange operator.
//
// myContext -- pointer to optimization context for this Exchange
// operator.
//
// Output:
// countOfStreams -- degree of parallelism for this Exchange (i.e. number of
// consumers for this Exchange.)
//
// Return:
// Pointer to computed cost object for this exchange operator.
//
//==============================================================================
Cost*
CostMethodExchange::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( op != NULL );
CMPASSERT( myContext != NULL );
//-----------------------------------
// Downcast to an Exchange operator.
//-----------------------------------
Exchange* exch = (Exchange*)op;
op_ = op;
//--------------------------------------------------------------------------
// Exchange cost is calculated twice.
// First: When going down the tree. This cost is minimal and doesn't
// come close to reflecting the real cost. The reason is that we
// often don't know how many ESPs and DP2s we are dealing with.
// This information is supplied by the FileScan Costing.
//
// Second: When going up the tree. At this point we know everything we
// will know about filescan suppied information. Going up the
// tree we do a complete costing of the Exchange.
//--------------------------------------------------------------------------
const CostScalar numOfProbes =
( myContext->getInputLogProp()->getResultCardinality() ).minCsOne();
const ReqdPhysicalProperty* rpp = myContext->getReqdPhysicalProperty();
//------------------------------------------------------------------------
// If we have not synthesized physical properties, we are going down
// the tree, so do a fast, pessimistic computation with minimal knowledge.
//------------------------------------------------------------------------
sppForMe_ = (PhysicalProperty *) myContext->
getPlan()->getPhysicalProperty();
if ( NOT sppForMe_)
return computeExchangeCostGoingDown( rpp, numOfProbes, countOfStreams );
//============================================================================
// * * * O N O U R W A Y B A C K U P * * *
//
// THIS IS WHERE THE REAL EXCHANGE COSTING IS DONE
//
// An Exchange operator acts as a broker of sorts between a set of "consumer"
// processes (known collectively as the "parent") and a set of "producer"
// processes (known collectively as the "child"). The parent sends requests
// down to the child, and the child returns rows up to the parent. The parent
// can bundle multiple requests in a single message buffer, and the child can
// bundle multiple rows in a single message buffer. Computing the cost of an
// exchange involves the following steps:
//
// 1. Compute number of "down" messages from parent to child.
// 2. Compute number of "up" messages from child to parent.
// 3. Determine how many "down" messages cross node boundaries and how many
// "up" messages cross node boundaries.
// 4. Produce first row and last row cost vectors for both the parent and
// the child using the message counts from step 3.
// 5. Compute the final Exchange operator cost from the vectors produced in
// step four.
//
// There are four basic Exchange situations that we need to cost:
//
// 1. dp2(s) -> master
// 2. esps -> master
// 3. dp2s -> esps
// 4. esps -> esps (this involves either repartitioning or replication)
//
// Fortunately, we don't need special code for each different case. We need
// to distinguish between DP2 Exchanges (cases 1 and 3) and ESP Exchanges
// (cases 2 and 4) at certain points in the code, but for the most part the
// exchange costing code applies to all cases. Nevertheless, anyone who plans
// on changing the code in the future should keep these cases in mind.
//
// A final point. We amortize the cost of an Exchange over the number of
// consumers that will execute concurrently (i.e. the parent's degree of
// parallelism).
//============================================================================
//------------------------------------------------------------
// Get the physical properties for the child of the Exchange.
//------------------------------------------------------------
const PhysicalProperty* sppForChild =
myContext->getPhysicalPropertyOfSolutionForChild(0);
sppForChild_ = (PhysicalProperty*) sppForChild;
numOfProbes_ = (CostScalar )numOfProbes;
isOpBelowRoot_ = (*CURRSTMT_OPTGLOBALS->memo)[myContext->getGroupId()]->isBelowRoot();
const PartitioningFunction* const childPartFunc =
sppForChild->getPartitioningFunction();
const PartitioningFunction* const myPartFunc =
myContext->getPlan()->getPhysicalProperty()->getPartitioningFunction();
NABoolean executeInDP2 = sppForChild->executeInDP2();
//--------------------------------------------------------------------------
// Compute number of producer processes and consumer processes associated
// with this Exchange.
//
// Also compute number of physical partitions. This may exceed the number
// of producers in the case of a logical partitioning strategy known as
// "partition grouping". We need number of active partitions to compute the number
// of "down" messages.
// Note: Taking into account the "current count of CPUs" is OK for now
// because we currently use one (reader) ESP per CPU. This may change in the
// future as the amount of data per CPU increases. - Sunil
//--------------------------------------------------------------------------
const CostScalar& numOfConsumers = ((NodeMap *)(myPartFunc->getNodeMap()))->
getNumActivePartitions();
const CostScalar& numOfPartitions = ((NodeMap *)(childPartFunc->getNodeMap()))->
getEstNumActivePartitionsAtRuntime();
const CostScalar& numOfProducers = MINOF( numOfPartitions ,sppForChild->getCurrentCountOfCPUs() );
//---------------------------------------------------------------
// Exchange operator's number of streams is parent's degree of
// parallelism (i.e. number of concurrently executing consumers).
//---------------------------------------------------------------
countOfStreams = Lng32(numOfConsumers.getValue());
//------------------------------------------------------------
// Get default values needed for subsequent Exchange costing.
//------------------------------------------------------------
CostScalar messageSpacePerRecordInKb;
CostScalar messageHeaderInKb;
CostScalar messageBufferSizeInKb;
getDefaultValues(executeInDP2,
exch,
messageSpacePerRecordInKb,
messageHeaderInKb,
messageBufferSizeInKb);
if (NOT executeInDP2)
exch->computeBufferLength(myContext,
numOfConsumers,
numOfProducers,
upMessageBufferLength_,
downMessageBufferLength_);
else
{
upMessageBufferLength_=messageBufferSizeInKb;
downMessageBufferLength_=csOne;
}
//------------------------------------------------------------------
// Compute the number of "down" messages.
//
// DownMessages are the messages flowing from the PA to root in DP2
// or from a parent ESP to a child ESP (i.e. from a Send-top
// operator to a Send-bottom operator).
//------------------------------------------------------------------
CostScalar downMessages = computeDownMessages(numOfProbes,
executeInDP2,
messageHeaderInKb,
messageBufferSizeInKb,
numOfPartitions,
numOfConsumers,
myContext,
downMessageLength_);
//---------------------------------------------------------------------
// Compute the number of "up" messages.
//
// UpMessages for executeInDP2 are those flowing from root in DP2 to PA.
// We make pushing groupby's attractive even if without them we would
// send back only one buffer. So for up messages we do not round the
// last buffer up to a full buffer.
//
// UpMessages for ESP to ESP exchanges are the number of
// messages needed to do the repartition or replication
//---------------------------------------------------------------------
CostScalar upMessages = computeUpMessages(myContext,
exch,
myPartFunc,
childPartFunc,
sppForChild,
messageSpacePerRecordInKb,
messageHeaderInKb,
messageBufferSizeInKb,
numOfConsumers,
executeInDP2,
upRowsPerConsumer_,
numOfContinueDownMessages_);
// each continue message is about 60 bytes
if (CmpCommon::getDefault(COMP_BOOL_60) == DF_ON)
downMessages = downMessages + numOfContinueDownMessages_;
downMessageLength_ = downMessageLength_ +
CostScalar(60) * numOfContinueDownMessages_;
//----------------------------------------------------------------------
// Is merging of streams possibly needed?
//----------------------------------------------------------------------
isMergeNeeded_ = (sppForMe_->getSortOrderType() != DP2_SOT) &&
(NOT sppForMe_->getSortKey().isEmpty());
//-------------------------------------------------------------------------
// Given the number of "up" and "down" messages, now determine how many of
// each type cross node boundaries.
//-------------------------------------------------------------------------
CostScalar downIntraNodeMessages,
downInterNodeMessages,
downRemoteNodeMessages,
upIntraNodeMessages,
upInterNodeMessages,
upRemoteNodeMessages;
categorizeMessages(myPartFunc,
childPartFunc,
executeInDP2,
downMessages,
upMessages,
downIntraNodeMessages,
downInterNodeMessages,
downRemoteNodeMessages,
upIntraNodeMessages,
upInterNodeMessages,
upRemoteNodeMessages);
//-------------------------------------------------------------------------
// Produce first row and last row cost vectors for both the parent and the
// child using the message information above.
//-------------------------------------------------------------------------
CostVecPtr myFR = NULL;
CostVecPtr myLR = NULL;
CostVecPtr childFR = NULL;
CostVecPtr childLR = NULL;
produceCostVectors(numOfProbes,
numOfConsumers,
numOfProducers,
executeInDP2,
myPartFunc,
childPartFunc,
messageSpacePerRecordInKb,
messageHeaderInKb,
messageBufferSizeInKb,
upRowsPerConsumer_,
downIntraNodeMessages,
downInterNodeMessages,
downRemoteNodeMessages,
upIntraNodeMessages,
upInterNodeMessages,
upRemoteNodeMessages,
myFR,
myLR,
childFR,
childLR);
// adjust the Last Row / First Row Cost for an exchange
// operator on top of a Partial GroupBy Leaf node
const RelExpr * myImmediateChild = myContext->
getPhysicalExprOfSolutionForChild(0);
const RelExpr * myGrandChild = myContext->
getPhysicalExprOfSolutionForGrandChild(0,0);
ValueIdSet immediateChildPartKey =
childPartFunc->getPartitioningKey();
const PhysicalProperty* sppForGrandChild =
myContext->
getPhysicalPropertyOfSolutionForGrandChild(0,0);
PartitioningFunction * grandChildPartFunc =
(sppForGrandChild?
sppForGrandChild->getPartitioningFunction():
NULL);
ValueIdSet grandChildPartKey;
if(grandChildPartFunc)
grandChildPartKey =
grandChildPartFunc->
getPartitioningKey();
NABoolean myChildIsExchange = FALSE;
NABoolean myChildIsSortOnTopOfHashPartGbyLeaf = FALSE;
if (myImmediateChild)
{
if ((myImmediateChild->getOperatorType() == REL_SORT) &&
myGrandChild &&
(myGrandChild->getOperatorType() == REL_HASHED_GROUPBY) &&
(((GroupByAgg*)myGrandChild)->isAPartialGroupByLeaf()) &&
(CmpCommon::getDefault(COMP_BOOL_103) == DF_OFF))
myChildIsSortOnTopOfHashPartGbyLeaf = TRUE;
if ((myImmediateChild->getOperatorType() == REL_EXCHANGE) &&
(CmpCommon::getDefault(COMP_BOOL_186) == DF_ON))
myChildIsExchange = TRUE;
}
// childToConsider will be either the immediate child of this
// exchange node, or the grand child. It will be the grand child
// in case there is another exchange below this exchange i.e.
// this exchange is on top of a PA. The grand child is only used
// in case we want to influence the cost for partial grouping in
// dp2. By default we don't adjust the cost for partial grouping
// in dp2, but if COMP_BOOL_186 is ON then we allow cost adjustments
// for exchange on top partial grouping in DP2.
const RelExpr * childToConsider =
((myChildIsExchange || myChildIsSortOnTopOfHashPartGbyLeaf)?
myGrandChild:myImmediateChild);
ValueIdSet bottomPartKey =
((myChildIsExchange)?grandChildPartKey:immediateChildPartKey);
if (childToConsider &&
(!executeInDP2) &&
((childToConsider->getOperatorType() == REL_HASHED_GROUPBY)||
(childToConsider->getOperatorType() == REL_ORDERED_GROUPBY))&&
(((GroupByAgg*)childToConsider)->isAPartialGroupByLeaf()))
{
ValueIdSet childGroupingColumns =
((GroupByAgg*)childToConsider)->groupExpr();
NABoolean childMatchesPartitioning = FALSE;
if (childGroupingColumns.contains(bottomPartKey))
childMatchesPartitioning = TRUE;
if (!childMatchesPartitioning)
{
CostScalar grpByAdjFactor = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(ROBUST_PAR_GRPBY_EXCHANGE_FCTR);
(*myLR) *= grpByAdjFactor;
(*myFR) *= grpByAdjFactor;
(*childLR) *= grpByAdjFactor;
(*childFR) *= grpByAdjFactor;
}
}
CostScalar ocbAdjustFactor_2 = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(COMP_FLOAT_5);
CostScalar ocbAdjustFactor_3 = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(COMP_INT_34);
CostScalar ocbAdjustFactor_4 = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(COMP_INT_35);
CostScalar ocbAdjustFactor_5 = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(COMP_INT_36);
const InputPhysicalProperty* ippForMe =
myContext->getInputPhysicalProperty();
const ValueIdSet& inVis = op_->getGroupAttr()->getCharacteristicInputs();
ValueIdSet outerRef;
inVis.getOuterReferences(outerRef);
NABoolean hasOuterReferences = (NOT outerRef.isEmpty());
NABoolean isUnderNestedJoin=
( hasOuterReferences OR numOfProbes.isGreaterThanOne() );
const PartitioningFunction* const parentPartFunc =
myContext->getPlan()->getPhysicalProperty()->getPartitioningFunction();
//--------------------------------------------------------------
// Compute Exchange cost using the cost vectors produced above.
//--------------------------------------------------------------
Cost* exchangeCost = computeExchangeCost(myFR,
myLR,
childFR,
childLR,
numOfConsumers,
numOfProducers);
//-----------------------------------------------
// As good citizens we clean up after ourselves.
//-----------------------------------------------
delete myFR;
delete myLR;
delete childFR;
delete childLR;
return exchangeCost;
} // CostMethodExchange::computeOperatorCostInternal()
//<pb>
// -----------------------------------------------------------------------
// CostMethodExchange::computeEspCost(NABoolean executeInEsp)
//------------------------------------------------------------------------
CostScalar
CostMethodExchange::computeESPCost ( const NABoolean executeInESP
, const CostScalar & numOfProbes) const
{
CostScalar espCPUTime ( csZero );
CostScalar espIOTime( csZero );
if ( executeInESP )
{
espCPUTime = CostPrimitives::getBasicCostFactor( CPUCOST_ESP_INITIALIZATION )
* CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// Assuming 2 seeks and 4k of virtual memory, i.e. 4Kbytes IO transfer
// espIOTime = CostScalar( 2.0 * CURRSTMT_OPTDEFAULTS->getTimePerSeek() +
// 4.0 *CURRSTMT_OPTDEFAULTS->getTimePerSeqKb() );
// IO component was commented out after WM bencmark. It was not clear why it was
// put here in the first place. Nov.2005. SP.
}
// return new STMTHEAP SimpleCostVector( espCPUTime, /* CPUTime */
//espIOTime, /* IOTime */
//csZero, /* MSGTime */
//csZero, /* idle time */
//numOfProbes ); /* num probes */
return espCPUTime;
} // CostMethodExchange::computeESPCost()
//<pb>
// ------------------------------------------------------------------------------
// CostMethodExchange::computeExchangeCostGoingDown
// ------------------------------------------------------------------------------
Cost*
CostMethodExchange::computeExchangeCostGoingDown( const ReqdPhysicalProperty* rpp,
const CostScalar & numOfProbes,
Lng32& countOfStreams)
{
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( rpp != NULL );
// The cost of initializing an Exchange is equivalent to 1000 cpu instructions
// and the 1000 instructions converted into CPUTime.
const CostScalar & cpuTime = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions() ;
// The execution model for the Exchange is such that the result that
// is produced by its child, i.e., a set of producer processes, is
// distributed to one or more consumer processes. The preliminary
// cost takes into account the initialization of communication for
// the consumers of data. If ESPs are not persistent, this cost
// can include a process startup cost.
// The cost of data transfer cannot be established until a plan for
// the child is available.
// Note: We should use the numOfProbes to compute the MSGTime in
// object cv. Doing so would lead to better pruning of plans.- Sunil
SimpleCostVector cv(
cpuTime,
csZero, /* no IOTime */
csZero, /* no MSGTime */
csZero, /* idle time */
numOfProbes /* num probes */
);
CostScalar espCount;
PartitioningRequirement* myPartReq = rpp->getPartitioningRequirement();
if ( ( myPartReq != NULL )
AND ( myPartReq->getCountOfPartitions() != ANY_NUMBER_OF_PARTITIONS )
)
{
espCount = myPartReq->getCountOfPartitions();
}
else
{
espCount = rpp->getCountOfPipelines();
}
//---------------------------------------------------------------
// Exchange operator's number of streams is parent's degree of
// parallelism (i.e. number of concurrently executing consumers).
// Note: Use of ceiling function causes over-estimation of plan
// fragments per cpu (when espPerCpu is multiplied by the
// # of active cpus). -- Sunil
//---------------------------------------------------------------
countOfStreams = (Lng32)espCount.value();
CostScalar activeCPUs = MINOF( espCount,
CostScalar(rpp->getCountOfAvailableCPUs()) );
CostScalar espsPerCPU = (espCount / activeCPUs).getCeiling();
return new STMTHEAP Cost( &cv,
&cv,
NULL,
Lng32(activeCPUs.getValue()),
Lng32(espsPerCPU.getValue())
);
} // CostMethodExchange::computeExchangeCostGoingDown
//<pb>
//==============================================================================
// Compute default values needed for costing a specified exchange operator.
//
// Input:
// executeInDP2 -- TRUE if child executes in DP2; FALSE otherwise.
//
// exch -- pointer to specified Exchange operator.
//
// Output:
// messageSpacePerRecordInKb -- size of a record (including any header
// overhead) in KB.
//
// messageHeaderInKb -- size of message buffer header in KB.
//
// messageBufferSizeInKb -- size of entire message buffer in KB.
//
// Return:
// none
//
//==============================================================================
void
CostMethodExchange::getDefaultValues(
const NABoolean executeInDP2,
const Exchange* exch,
CostScalar& messageSpacePerRecordInKb,
CostScalar& messageHeaderInKb,
CostScalar& messageBufferSizeInKb) const
{
//-------------------------------------------------------------------------
// Determine message buffer size. Messages to DP2 have a different buffer
// size than messages sent to another ESP.
// Note: The generator (Exchange::codeGenForESP) might choose a different
// buffer sizes. Need to get both code routines in sync. -- Sunil
//-------------------------------------------------------------------------
if (executeInDP2)
messageBufferSizeInKb =
CostPrimitives::getBasicCostFactor(DP2_MESSAGE_BUFFER_SIZE);
else
messageBufferSizeInKb =
CostPrimitives::getBasicCostFactor(OS_MESSAGE_BUFFER_SIZE);
//-------------------------------------------
// Get addressability to the defaults table.
//-------------------------------------------
NADefaults &defs = ActiveSchemaDB()->getDefaults();
//-----------------------------------------------------------------
// DP2 adds a row header to each row placed in the message buffer.
//
// Note: it's not clear to me why DP2 is involved in data transfer
// or why DP2 needs to add a row header to each row.
// It's the EID that moves data from the DP2 cache into its buffer
// and then sends it to the parent fragment (master or an ESP).
// The buffer containing the data is a SQLBuffer object, with its
// own header, format, etc. In the case of DP2-exchange, the file
// system places its own header (IPC protocol overhead) on top of
// the SQLBuffer. In the case of ESP-exchange, the send-top/send-bottom
// protocol adds its own header. These additional overhead need
// to be incorporated into the code below. -- Sunil
//-----------------------------------------------------------------
const CostScalar recordHeaderInBytes =
defs.getAsLong( DP2_MESSAGE_HEADER_SIZE_BYTES ); // 18
//--------------------------------------------------------------------
// Compute size (in KB) of a row including its associated row header.
//--------------------------------------------------------------------
const GroupAttributes* childGA = exch->child(0).getGroupAttr();
messageSpacePerRecordInKb =
( recordHeaderInBytes + childGA->getRecordLength() ) / csOneKiloBytes;
//------------------------------------------------------------------------
// Determine message header size (in KB). Ensure that the size of
// this header and the size of one row (including the row header) does
// not exceed the size of the message buffer. In other words, we need to
// ensure that we have enough message buffer space to send back at least
// one row.
//------------------------------------------------------------------------
messageHeaderInKb = defs.getAsDouble( DP2_MESSAGE_HEADER_SIZE );
// 18/1024. == 0.0176
//Throw an assertion if the Message Record length is greater than that
//of buffer size on a local node which is currently 51Kb and if
//the size is more than 51 then the execution will fail, it is not compared
//with DP2_MESSAGE_BUFFER_SIZE as the size is different on local and remote
//node 51KB on local, 32KB on remote as of 03/15/2002, so compared with the
//larger value 51KB.
//Note: At some point in the near future, the maximum remote message size will
// be the same as the max. local message size
// Meantime COMP_BOOL_140 can be used to avoid this check and allow for
// longer rows; e.g. for testing (for Hash-Join the row length is checked
// in the generator )
if ( CmpCommon::getDefault(COMP_BOOL_140) == DF_OFF ) {
CMPASSERT( (CostScalar)
CostPrimitives::getBasicCostFactor(LOCAL_MESSAGE_BUFFER_SIZE)
>= (messageSpacePerRecordInKb + messageHeaderInKb) );
}
} // CostMethodExchange::getDefaultValues()
//<pb>
//==============================================================================
// Compute number of messages sent from parent of an exchange operator down to
// its child.
//
// Input:
// numOfProbes -- number of requests sent from parent to child.
//
// messageHeaderInKb -- size of message buffer header in KB.
//
// messageBufferSizeInKb -- size of entire message buffer in KB.
//
// numOfPartitions -- number child processes actually receiving
// messages.
//
// Output:
// none
//
// Return:
// Number of messages sent down to child.
//
//==============================================================================
CostScalar
CostMethodExchange::computeDownMessages(
const CostScalar& numOfProbes,
const NABoolean executeInDP2,
const CostScalar& messageHeaderInKb,
const CostScalar& messageBufferSizeInKb,
const CostScalar& numOfPartitions,
const CostScalar& numOfConsumers,
const Context* myContext,
CostScalar &downMessageLength) const
{
if (CmpCommon::getDefault(COMP_BOOL_60) == DF_ON)
{
return computeDownDataAndControlMessages(numOfProbes,
executeInDP2,
messageHeaderInKb,
messageBufferSizeInKb,
numOfPartitions,
numOfConsumers,
myContext,
downMessageLength);
}
//--------------------------------------------------------
// If this is an ESP exchange (not executeInDP2)
// the number of messags is the number of probes times 3;
//
// Note: The factor of 3 comes from a start message, a message
// containing one probe or request, and a stop message.
// The formula below does not work for a TSJ operator where
// multiple probes are sent to a child. These probes are
// buffered; hence, the start/stop overhead is incurred not
// on a probe-basis but on a buffer-basis. The formula needs
// to take buffering into account. (A TSJ operator is used
// for inserts and for nested joins.) - Sunil
//
// Note: Why multiply by the numOfConsumers (upper ESPs) ????????
// The numOfProbes is already cumulative for the entire CascadesGroup!!
// We multiply by numOfPartitions because all probes
// always go to all lower ESPs. - Sunil
//--------------------------------------------------------
// This logic is definitely wrong. sending
// probes down from ESP to DP2 is very similar to sending probes
// dow from ESP to ESP and could/should have similar cost.
// So I'll use comp_bool_29 to bypass the difference between
// these 2 cases if it is OFF - default value.
if (NOT executeInDP2 AND (CmpCommon::getDefault(COMP_BOOL_29) == DF_OFF))
{
downMessageLength=numOfProbes * 3 * numOfPartitions*numOfConsumers;
return (numOfProbes * 3 * numOfPartitions*numOfConsumers);
}
//-------------------------------------------
// Get addressability to the defaults table.
//-------------------------------------------
NADefaults &defs = ActiveSchemaDB()->getDefaults();
//----------------------------------------------------
// Determine size of end-of-buffer indicator (in KB).
// Determine size of a request (in KB).
//----------------------------------------------------
const CostScalar endOfBufferHeaderInKb =
defs.getAsDouble(DP2_END_OF_BUFFER_HEADER_SIZE); //32./1024 == 0.0313
const CostScalar requestInKb =
defs.getAsDouble(DP2_EXCHANGE_REQUEST_SIZE); // 48/1024 == 0.0469
//------------------------------------------------------
// Assume uniform distribution of numOfProbes over PA's.
// Note: In the case of DP2-exchange, for hash-partitioned
// tables, a probe will go to all partitions unless constant
// values are available for all partitioning-key columns.
// In order to represent this notion, we need code
// here that's similar to code used to compute
// "repeat count" in the costing of a scan operator. - Sunil
//------------------------------------------------------
//---------------------------------------------------------------------
// we change number of probes if skewness is to be considered
// this is true for type-1 nested joins
// for type-2 nested joins: the one with skew in dp2 would be very busy
// so that is ok
//---------------------------------------------------------------------
CostScalar probesToBeCosted = ( numOfProbes / numOfPartitions ).minCsOne();
const PhysicalProperty* sppForChild =
myContext->getPhysicalPropertyOfSolutionForChild(0);
const PartitioningFunction* const childPartFunc =
sppForChild->getPartitioningFunction();
PartitioningFunction* phys= NULL;
if (childPartFunc->isALogPhysPartitioningFunction())
phys = childPartFunc->
castToLogPhysPartitioningFunction()->
getPhysPartitioningFunction();
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) && (phys != NULL))
{
CostScalar probesFromBusStream = csMinusOne;
// check if this exchange is over a scan node
if(sppForChild->getDP2CostThatDependsOnSPP() )
probesFromBusStream = sppForChild->
getDP2CostThatDependsOnSPP()->
getProbesAtBusiestStream();
probesToBeCosted = MAXOF(probesFromBusStream, probesToBeCosted);
}
const CostScalar numOfProbesPerPartitionInKb =
probesToBeCosted * requestInKb;
DP2CostDataThatDependsOnSPP *dp2CostInfo =
(DP2CostDataThatDependsOnSPP *) sppForChild->getDP2CostThatDependsOnSPP();
//------------------------------------------------------------
// Down Messages:
// Figure out the number of messages to dp2 (downMessages)
// A down message is sent to every active partition that
// will return rows for a request.
// (numOfProbes are packed in a down message; a down message
// will in general have as many numOfProbes as there are
// entries in the PA down queue). We need to take into
// account that there may be several partitions;
//
// Down Messages for ESP exchanges use the same fields as
// dp2 down messages.
//
// Assume that all probes will be packed contiguously into
// the down message buffer as if all of them were immediately
// available (this is the best case. In the worst case
// every request is sent down in its own buffer):
//------------------------------------------------------------
CostScalar downMessagesPerPartition = numOfProbesPerPartitionInKb
/ ( messageBufferSizeInKb
- endOfBufferHeaderInKb);
downMessagesPerPartition = ( downMessagesPerPartition ).minCsOne();
downMessageLength=downMessagesPerPartition * numOfPartitions;
return downMessagesPerPartition * numOfPartitions;
} // CostMethodExchange::computeDownMessages()
//****************************************************************************
// computeDownDataAndControlMessages()
//****************************************************************************
CostScalar
CostMethodExchange::computeDownDataAndControlMessages(
const CostScalar& numOfProbes,
const NABoolean executeInDP2,
const CostScalar& messageHeaderInKb,
const CostScalar& messageBufferSizeInKb,
const CostScalar& numOfPartitions,
const CostScalar& numOfConsumers,
const Context* myContext,
CostScalar &downMessageLength) const
{
CostScalar inputRowSize = op_->getGroupAttr()->
getCharacteristicInputs().getRowLength();
if (NOT executeInDP2 )
{
//if (NOT inputRowSize.isLessThanOne())
// Assume minimum of 28 bytes with each probe
inputRowSize = inputRowSize + CostScalar(28);
CostScalar numConnections = MINOF(
ActiveSchemaDB()->getDefaults().getAsLong
(GEN_SNDT_NUM_BUFFERS),
ActiveSchemaDB()->getDefaults().getAsLong
(GEN_SNDB_NUM_BUFFERS) );
CostScalar numOfMessages = CostScalar(2) * numOfConsumers *
numOfPartitions * numConnections;
downMessageLength = CostScalar(64) * numOfPartitions*
numOfConsumers + // A
inputRowSize * numOfPartitions*
numOfConsumers + // B
CostScalar(10000) * numOfPartitions; // D
// we are charging startup overhead of exchange operator here.
// (A) File Open
// (B) Open and sending input with request
// (C) Continue message to be calculated after upMessages are calculated
// Note that these are really short messages unlike upward messages which
// tend to be buffered messages
// (D) Fixed overhead: 10k * number of producers to compete with Dp2
// exchanges
return numOfMessages;
}
downMessageLength = csZero;
//-------------------------------------------
// Get addressability to the defaults table.
//-------------------------------------------
NADefaults &defs = ActiveSchemaDB()->getDefaults();
//----------------------------------------------------
// Determine size of end-of-buffer indicator (in KB).
// Determine size of a request (in KB).
//----------------------------------------------------
const CostScalar endOfBufferHeaderInKb =
defs.getAsDouble(DP2_END_OF_BUFFER_HEADER_SIZE); //32./1024 == 0.0313
const CostScalar requestInKb =
defs.getAsDouble(DP2_EXCHANGE_REQUEST_SIZE); // 48/1024 == 0.0469
// this is true for type-1 nested joins
// for type-2 nested joins: the one with skew in dp2 would be very busy
// so that is ok
//---------------------------------------------------------------------
CostScalar probesToBeCosted = ( numOfProbes / numOfPartitions ).minCsOne();
const PhysicalProperty* sppForChild =
myContext->getPhysicalPropertyOfSolutionForChild(0);
const PartitioningFunction* const childPartFunc =
sppForChild->getPartitioningFunction();
PartitioningFunction* phys= NULL;
if (childPartFunc->isALogPhysPartitioningFunction())
phys = childPartFunc->
castToLogPhysPartitioningFunction()->
getPhysPartitioningFunction();
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) && (phys != NULL))
{
CostScalar probesFromBusStream = csMinusOne;
// check if this exchange is over a scan node
if (sppForChild->getDP2CostThatDependsOnSPP())
probesFromBusStream = sppForChild->
getDP2CostThatDependsOnSPP()->
getProbesAtBusiestStream();
probesToBeCosted = MAXOF(probesFromBusStream, probesToBeCosted);
}
CostScalar numOfProbes1=probesToBeCosted*numOfPartitions;
CostScalar inputSizeInKb =
numOfProbes1 * requestInKb + numOfProbes1 * inputRowSize/1024 ;
//assume data messages are buffered when computing number of messages
CostScalar downDataMessages = (inputSizeInKb / ( messageBufferSizeInKb
- endOfBufferHeaderInKb)).minCsOne();
const PartitioningFunction* const parentPartFunc =
myContext->getPlan()->getPhysicalProperty()->getPartitioningFunction();
CostScalar paPartsPerGroup;
NABoolean parentGroupsChildren;
// Check if the operator has outer col references (under a NestedJoin).
const ValueIdSet& inVis = op_->getGroupAttr()->getCharacteristicInputs();
ValueIdSet outerRef;
inVis.getOuterReferences(outerRef);
NABoolean hasOuterReferences = (NOT outerRef.isEmpty());
NABoolean isUnderNestedJoin=
( hasOuterReferences OR numOfProbes.isGreaterThanOne() );
// are we doing grouping; if so increase number of down messages
// by number of PAs in a group
NABoolean isGroupingDone = isGroupedRepartitioning(childPartFunc,
parentPartFunc,
parentGroupsChildren,
paPartsPerGroup);
NABoolean inAType2Join = FALSE;
if (parentPartFunc->isAReplicateNoBroadcastPartitioningFunction() &&
myContext->getInputPhysicalProperty())
{
const PartitioningFunction* const njParentPartFunc =
myContext->getInputPhysicalProperty()->
getNjOuterOrderPartFuncForNonUpdates();
if (njParentPartFunc != NULL)
{
isGroupingDone = isGroupedRepartitioning(childPartFunc,
njParentPartFunc,
parentGroupsChildren,
paPartsPerGroup);
if (isGroupingDone)
inAType2Join = TRUE;
}
}
CostScalar downControlMessageLength(csZero), downControlMessages(csZero);
CostScalar numOfPartitions1=numOfPartitions;
if (NOT isUnderNestedJoin)
{
downControlMessageLength= CostScalar(10000) * numOfPartitions;
downControlMessages = numOfPartitions;
downDataMessages = downDataMessages*numOfPartitions;
// what should we do for grouping here?
}
else
{
// this is how probes are compressd;
CostScalar compressionFactor = defs.getAsLong(COMP_INT_60);
const InputPhysicalProperty* ippForMe =
myContext->getInputPhysicalProperty();
// no compression if it is zero
if (compressionFactor == csZero)
compressionFactor = messageBufferSizeInKb;
DP2CostDataThatDependsOnSPP *dp2CostInfo =
(DP2CostDataThatDependsOnSPP *) sppForChild->getDP2CostThatDependsOnSPP();
/*if (isGroupingDone)
downDataMessages = downDataMessages * paPartsPerGroup;*/
if (dp2CostInfo !=NULL)
{
switch(dp2CostInfo->getRepeatCountState())
{
case DP2CostDataThatDependsOnSPP::KEYCOLS_COVERED_BY_CONST:
{
downControlMessageLength= CostScalar(10000);
downControlMessages = csOne;
// Assumption about how tightly probes are packed.
downDataMessages = downDataMessages *
(messageBufferSizeInKb/compressionFactor.minCsOne()).minCsOne();
break;
}
case DP2CostDataThatDependsOnSPP::KEYCOLS_COVERED_BY_PROBE_COLS_CONST:
{
// is this a type-2 join?
// assumption about how tightly probes are packed
downDataMessages = downDataMessages *
(messageBufferSizeInKb/compressionFactor.minCsOne()).minCsOne();
if (parentPartFunc->isAReplicateNoBroadcastPartitioningFunction())
{
CostScalar actualMessages = MINOF( numOfPartitions *numOfConsumers,
numOfProbes);
if (inAType2Join )
{
actualMessages = MINOF(actualMessages,
numOfConsumers * paPartsPerGroup);
CostScalar adjFactor = defs.getAsLong(COMP_INT_61);
if (adjFactor.isGreaterThanZero())
{
if (adjFactor == csOne)
actualMessages = MINOF(actualMessages, paPartsPerGroup);
else
actualMessages = MINOF(actualMessages, adjFactor);
}
}
downControlMessageLength= CostScalar(10000) * actualMessages;
downControlMessages = actualMessages;
}
else
{
CostScalar actualMessages = MINOF(numOfPartitions, numOfProbes);
downControlMessageLength= CostScalar(10000) * actualMessages;
downControlMessages = actualMessages;
}
break;
}
case DP2CostDataThatDependsOnSPP::KEYCOLS_NOT_COVERED:
case DP2CostDataThatDependsOnSPP::UNKNOWN:
{
downControlMessageLength=
CostScalar(10000)* numOfPartitions * numOfConsumers;
downControlMessages = numOfPartitions * numOfConsumers ;
if( myContext->getReqdPhysicalProperty()->getOcbEnabledCostingRequirement() )
{
downControlMessages = MINOF(
numOfPartitions *numOfConsumers,
numOfProbes);
CostScalar interMedLength = CostScalar(10000)*numOfProbes;
downControlMessageLength=MINOF(downControlMessageLength,
interMedLength);
}
if ( myContext->getReqdPhysicalProperty()->getOcbEnabledCostingRequirement() )
{
downControlMessages = MINOF(numOfPartitions * numOfConsumers,
numOfProbes);
CostScalar interMedLength = CostScalar(10000) * numOfProbes;
downControlMessageLength = MINOF(downControlMessageLength,
interMedLength);
}
// Assumption about how tightly probes are packed.
// if COMP_INT_60 = 1 then 1 k buffers are sent
// if it is 2 then 2k buffers sent
// if it is 56 then buffers of 56k are sent (default)
downDataMessages = downDataMessages *
(messageBufferSizeInKb/compressionFactor.minCsOne()).minCsOne();
// All data goes to all partitions
downDataMessages = downDataMessages * numOfPartitions ;
inputSizeInKb = inputSizeInKb * numOfPartitions ;
break;
}
case DP2CostDataThatDependsOnSPP::UPDATE_OPERATION:
{
// make serial updates more expensive than parallel updates
downDataMessages = downDataMessages / numOfConsumers ;
if (parentPartFunc->isAReplicateNoBroadcastPartitioningFunction())
{
CostScalar actualMessages = MINOF(
numOfPartitions *numOfConsumers,
numOfProbes);
downControlMessageLength= CostScalar(10000) * actualMessages;
downControlMessages = actualMessages;
}
else
{
CostScalar actualMessages = MINOF(numOfPartitions, numOfProbes);
downControlMessageLength= CostScalar(10000) * actualMessages;
downControlMessages = actualMessages;
}
break;
}
}
}
} // Under Nested Join
//------------------------------------------------------------
// Down Messages:
// Figure out the number of messages to dp2 (downMessages)
// A down message is sent to every active partition that
// will return rows for a request.
// (numOfProbes are packed in a down message; a down message
// will in general have as many numOfProbes as there are
// entries in the PA down queue). We need to take into
// account that there may be several partitions;
//
// Down Messages for ESP exchanges use the same fields as
// dp2 down messages.
//
// Assume that all probes will be packed contiguously into
// the down message buffer as if all of them were immediately
// available (this is the best case. In the worst case
// every request is sent down in its own buffer):
//------------------------------------------------------------
downMessageLength = downControlMessageLength + inputSizeInKb *1024;
// continue messages are computed elsewhere
return downDataMessages+downControlMessages;
} // CostMethodExchange::computeDownMessages()
//<pb>
//==============================================================================
// Compute number of messages sent from child of a specified exchange operator
// up to its parent.
//
// Input:
// exch -- pointer to specified Exchange operator.
//
// parentContext -- pointer to optimization context for specified
// Exchange operator.
//
// parentPartFunc -- pointer to parent's partitioning function.
//
// childPartFunc -- pointer to child's partitioning function.
//
// sppForChild -- pointer to child's physical properties.
//
// messageSpacePerRecordInKb -- size of a record (including any header
// overhead) in KB.
//
// messageHeaderInKb -- size of message buffer header in KB.
//
// messageBufferSizeInKb -- size of entire message buffer in KB.
//
// numOfConsumers -- number parent processes actually receiving
// messages.
//
// executeInDP2 -- TRUE if child executes in DP2; FALSE otherwise.
//
// Output:
// upRowsPerConsumer -- number of output rows coming up to parent.
//
// Return:
// Number of messages sent from child up to parent.
//
//==============================================================================
CostScalar
CostMethodExchange::computeUpMessages(
const Context* parentContext,
Exchange* exch,
const PartitioningFunction* parentPartFunc,
const PartitioningFunction* childPartFunc,
const PhysicalProperty* sppForChild,
const CostScalar & messageSpacePerRecordInKb,
const CostScalar & messageHeaderInKb,
const CostScalar & messageBufferSizeInKb,
const CostScalar & numOfConsumers,
const NABoolean executeInDP2,
CostScalar& upRowsPerConsumer,
CostScalar& numOfContinueDownMessages) const
{
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( parentContext != NULL );
CMPASSERT( exch != NULL );
CMPASSERT( parentPartFunc != NULL );
CMPASSERT( childPartFunc != NULL );
//---------------------------------------------------------------------
// Up messages:
// For DP2 access at least one up message is sent from every
// (active?) partition to the master. In general,
// a request can generate more than one up message
// but it must generate at least one (if only notifying that there
// were no matching records). In order to promote aggregates in DP2
// we do not cost full buffers for the last buffer.
//
// For ESP to ESP access the up messages are the messages that are
// either repartitioned or replicated.
//------------------------------------------------------------------
//---------------------------------------------
// Determine number of rows produced by child.
//---------------------------------------------
EstLogPropSharedPtr inputLP = parentContext->getInputLogProp();
CMPASSERT( inputLP );
EstLogPropSharedPtr childOutputLP = exch->child(0).outputLogProp(inputLP);
// Determine number of probes and whether there are any outer references
// (i.e. probe values)
const CostScalar& noOfProbes = ( inputLP->getResultCardinality() ).minCsOne();
ValueIdSet externalInputs( exch->getGroupAttr()->getCharacteristicInputs() );
ValueIdSet outerRefs;
externalInputs.getOuterReferences( outerRefs );
// Calculate number of rows each consumer will receive and then calculate
// the number of messages based on message overhead for each row and the
// size of the message buffer.
//------------------------------------------------------------------------
if (CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting())
{
upRowsPerConsumer = childOutputLP->
getCardOfBusiestStream(
parentPartFunc,
(Lng32)numOfConsumers.getValue(),
exch->getGroupAttr(),
(Lng32)numOfConsumers.getValue());
// assume number of consumers = number of CPUs;
// it is only used for round robin pf. where skew is not an issue
}
else
upRowsPerConsumer = childOutputLP->getResultCardinality() /
numOfConsumers;
//------------------------------------------------------------------------
// For broadcast replication, each child will send all rows to all consumers.
// For no broadcast replication underneath a materialize that is not
// passing the probes through, each consumer will read the rows of all
// the children. This is similar to broadcast replication, so what we
// want to do here is the same - multiply the number of rows by the
// by the number of consumers.
//------------------------------------------------------------------------
if ( parentPartFunc->isAReplicateViaBroadcastPartitioningFunction()
OR ( parentPartFunc->isAReplicateNoBroadcastPartitioningFunction()
AND noOfProbes == 1 // 1 probe, but
AND outerRefs.isEmpty() // no probe values - must be under a materialize
)
)
{
//---------------------------------------------------------------------
// All producers send all rows to all consumers.
//---------------------------------------------------------------------
upRowsPerConsumer = upRowsPerConsumer * numOfConsumers ;
}
CostScalar bufferLength;
if (CmpCommon::getDefault(COMP_BOOL_60) == DF_ON)
bufferLength = upMessageBufferLength_;
else
bufferLength = messageBufferSizeInKb;
CostScalar upMessagesPerConsumer =
( upRowsPerConsumer /
((bufferLength-messageHeaderInKb)/messageSpacePerRecordInKb ).getFloor()).getCeiling() ;
numOfContinueDownMessages = csZero;
if ( NOT upMessagesPerConsumer.isLessThanOne())
numOfContinueDownMessages = (upMessagesPerConsumer - csOne).
getCeiling() * numOfConsumers;
if ( NOT executeInDP2 )
{
//----------------------------------------------------------
// Producer processes are ESPs. Make sure we have at least
// one up message per consumer.
//----------------------------------------------------------
return MIN_ONE_CS( upMessagesPerConsumer ) * numOfConsumers;
}
else
{
//-----------------------------------------------------------
// Producer processes are DP2s. To give aggregates in DP2 a
// slight advantage, calculate a smaller number of messages
// when each consumer sends less than one message on average.
//-----------------------------------------------------------
if ( upMessagesPerConsumer.isLessThanOne() /* < csOne */ )
{
return MIN_ONE_CS((numOfConsumers - csOne) * upMessagesPerConsumer );
}
else
{
return upMessagesPerConsumer * numOfConsumers;
}
}
} // CostMethodExchange::computeUpMessages()
//**************************************************************************
// CostMethodExchange::isGroupedRepartitioning()
//
//**************************************************************************
NABoolean CostMethodExchange::isGroupedRepartitioning(
const PartitioningFunction* childPartFunc,
const PartitioningFunction* parentPartFunc,
NABoolean &parentGroupsChildren,
CostScalar& partsPerGroup) const
{
parentGroupsChildren = FALSE;
NABoolean childGroupsParent = FALSE;
Lng32 myPartsPerGroup=0;
NABoolean isParentSinglePF = (parentPartFunc->
castToSinglePartitionPartitioningFunction() != NULL);
NABoolean isChildSinglePF = (childPartFunc->
castToSinglePartitionPartitioningFunction() != NULL);
if (isParentSinglePF && isChildSinglePF)
return FALSE;
LogPhysPartitioningFunction *logPhys = (LogPhysPartitioningFunction *)
childPartFunc->
castToLogPhysPartitioningFunction();
if (logPhys != NULL)
{
childPartFunc = logPhys->getPhysPartitioningFunction();
}
if (parentPartFunc->isAGroupingOf(*childPartFunc, &myPartsPerGroup)
//AND !isParentSinglePF
)
{
parentGroupsChildren = TRUE;
}
else if (childPartFunc->isAGroupingOf(*parentPartFunc, &myPartsPerGroup))
{
childGroupsParent = TRUE;
}
partsPerGroup=myPartsPerGroup;
return ( (childGroupsParent OR parentGroupsChildren)
AND partsPerGroup >= 1);
}
void CostMethodExchange::categorizeMessagesForDP2(
const PartitioningFunction* parentPartFunc,
const PartitioningFunction* childPartFunc,
const CostScalar &downMessages,
const CostScalar & upMessages,
CostScalar & downIntraCpuMessages,
CostScalar & downIntraSegmentMessages,
CostScalar & downRemoteSegmentMessages,
CostScalar & upIntraCpuMessages,
CostScalar & upIntraSegmentMessages,
CostScalar & upRemoteSegmentMessages) const
{
// are there multiple segments? (or clusters in NodeMap notation)
const NodeMap* childNodeMap = childPartFunc->getNodeMap();
const NodeMap * parentNodeMap = parentPartFunc->getNodeMap();
NABoolean areThereMultiSegments = childNodeMap->isMultiCluster
(0,childNodeMap->getNumEntries(), TRUE);
if (!areThereMultiSegments)
areThereMultiSegments = parentNodeMap->isMultiCluster
(0,parentNodeMap->getNumEntries(), TRUE);
// number of producers, number of consumers
// can't use nodemaps here
const CostScalar& numOfConsumers = ((NodeMap *)(parentPartFunc->getNodeMap())
)->getNumActivePartitions();
const CostScalar& numOfPartitions = ((NodeMap *)(childPartFunc->getNodeMap()))
->getEstNumActivePartitionsAtRuntime();
const CostScalar& numOfProducers = MINOF( numOfPartitions,
sppForChild_->getCurrentCountOfCPUs());
const LogPhysPartitioningFunction* childLppf =
childPartFunc->castToLogPhysPartitioningFunction();
CostScalar intraSegmentFactor = CostScalar(15)/CostScalar(16);
CostScalar maxDegree = MAXOF(numOfProducers, numOfConsumers);
NABoolean isPAPartGroup =
(childLppf != NULL
&& (childLppf->getLogPartType() ==
LogPhysPartitioningFunction::PA_PARTITION_GROUPING));
NABoolean parentGroupsChildren = FALSE;
CostScalar paPartsPerGroup;
if ( (numOfConsumers == csOne) &&
NOT areThereMultiSegments &&
(numOfProducers >= csOne) )
// case 1: single segment: serial plan; only dp2 parallelism
{
downIntraCpuMessages=downMessages/numOfProducers;
downIntraSegmentMessages= downMessages - downIntraCpuMessages;
downRemoteSegmentMessages=csZero;
upRemoteSegmentMessages=csZero;
upIntraCpuMessages=upMessages/numOfProducers;
upIntraSegmentMessages=upMessages-upIntraCpuMessages;
}
else if ( (numOfConsumers == csOne) &&
areThereMultiSegments
)
{
// case 2: serial plan, multi-segment; same as Case 1
CostScalar numChildSegments = getNumberofSegments(childNodeMap);
downIntraCpuMessages=downMessages/numOfProducers;
downIntraSegmentMessages=downMessages/numChildSegments -
downIntraCpuMessages;
downRemoteSegmentMessages=downMessages -
downIntraSegmentMessages-
downIntraCpuMessages;
upIntraCpuMessages=upMessages/numOfProducers;
upIntraSegmentMessages=upMessages/numChildSegments -
upIntraCpuMessages;
upRemoteSegmentMessages=upMessages-upIntraSegmentMessages -
upIntraCpuMessages;
}
else if (( areThereMultiSegments &&
parentPartFunc->isAReplicateNoBroadcastPartitioningFunction()
) ||
( !areThereMultiSegments &&
parentPartFunc->isAReplicateNoBroadcastPartitioningFunction()
)
)
// case 3: type 2 nested join: similar to ESP communication pattern
// assumption: number of producers, consumers is a power of 2.
// IntraNode % (1/16) * interSegmentWeight
// InterSegment % (15/16) * interSegmentWeight
{
CostScalar localMessageWeight = computeLocalMessageWeight(childNodeMap,
parentNodeMap);
CostScalar intraNodeWeightFactor = csOne/maxDegree;
if (areThereMultiSegments)
{
downIntraCpuMessages= downMessages * localMessageWeight *
(csOne - intraSegmentFactor);
downIntraSegmentMessages= downMessages * localMessageWeight *
intraSegmentFactor;
downRemoteSegmentMessages= downMessages-downIntraSegmentMessages -
downIntraCpuMessages;
upIntraCpuMessages= upMessages * localMessageWeight *
(csOne - intraSegmentFactor);
upIntraSegmentMessages=upMessages* localMessageWeight *
intraSegmentFactor;
upRemoteSegmentMessages=upMessages - upIntraSegmentMessages -
upIntraCpuMessages;
}
else
{
// this is ok even if have 16 x 1; in that case down messages
// could be one. We are charging a fraction to intracpu and another
// fraction to intrasegment. But in fact there is just one message
// which is either intracpu or intrasegment, but not both.
upRemoteSegmentMessages=csZero;
downRemoteSegmentMessages=csZero;
downIntraCpuMessages=downMessages / maxDegree;
downIntraSegmentMessages=downMessages - downIntraCpuMessages;
upIntraCpuMessages=upMessages /maxDegree;
upIntraSegmentMessages=upMessages - upIntraCpuMessages;
}
}
else if ( areThereMultiSegments &&
numOfConsumers <= numOfProducers &&
isGroupedRepartitioning(childPartFunc,
parentPartFunc,
parentGroupsChildren,
paPartsPerGroup)
)
{
// remote messages decrease as we increase degree of parallelism:
// 1 x 128: all (almost all) are remote
// 128 x 128: all are local, that is, within segment
CostScalar intraCpuMessageWeight=csOne/paPartsPerGroup;
CostScalar remoteSegmentWeight, intraSegmentWeight;
if (paPartsPerGroup <= 16)
{
remoteSegmentWeight = csZero;
intraSegmentWeight = csOne - intraCpuMessageWeight;
}
else
{
remoteSegmentWeight = (paPartsPerGroup - 16)/paPartsPerGroup;
intraSegmentWeight = CostScalar(15) / paPartsPerGroup;
}
downIntraCpuMessages=downMessages * intraCpuMessageWeight;
downIntraSegmentMessages= downMessages * intraSegmentWeight;
downRemoteSegmentMessages=downMessages * remoteSegmentWeight;
upIntraCpuMessages=upMessages*intraCpuMessageWeight;
upIntraSegmentMessages=upMessages * intraSegmentWeight;
upRemoteSegmentMessages=upMessages * remoteSegmentWeight;
}
else if ( !areThereMultiSegments &&
(numOfConsumers <= numOfProducers) &&
isGroupedRepartitioning(childPartFunc,
parentPartFunc,
parentGroupsChildren,
paPartsPerGroup)
)
// case 6: single segment; number of consumers > 1.
// 2 x 16, 4x16, 8 x 16, 16x16 etc.
{
CostScalar intraCpuMessageWeight=csOne/paPartsPerGroup;
downIntraCpuMessages=downMessages * intraCpuMessageWeight;
downIntraSegmentMessages=downMessages - downIntraCpuMessages;
downRemoteSegmentMessages=csZero;
upIntraCpuMessages=upMessages * intraCpuMessageWeight;
upIntraSegmentMessages=upMessages-upIntraCpuMessages;
upRemoteSegmentMessages= csZero;
}
else
{
// default: when do we end up here? Sometimes isGroupingOf may not
// work
CostScalar intraSegmentWeight =
computeLocalMessageWeight(childNodeMap,parentNodeMap);
downIntraCpuMessages= downMessages * intraSegmentWeight *
(csOne-intraSegmentFactor);
downIntraSegmentMessages=downMessages * intraSegmentWeight *
intraSegmentFactor ;
downRemoteSegmentMessages=downMessages - downIntraSegmentMessages
- downIntraCpuMessages;
upIntraCpuMessages=upMessages * intraSegmentWeight *
(csOne-intraSegmentFactor);
upIntraSegmentMessages=upMessages * intraSegmentWeight *
intraSegmentFactor;
upRemoteSegmentMessages=upMessages - upIntraCpuMessages -
upIntraSegmentMessages;
}
DCMPASSERT(!upRemoteSegmentMessages.isLessThanZero());
DCMPASSERT(!downRemoteSegmentMessages.isLessThanZero());
} // CostMethodExchange::categorizeMessagesForDP2()
//****************************************************************************
//CostMethodExchange::getNumberofSegments()
//****************************************************************************
CostScalar CostMethodExchange::getNumberofSegments(const NodeMap* childNodeMap)
const
{
return CostScalar(childNodeMap->getNumEntries()/16).minCsOne();
}// CostMethodExchange::getNumberofSegments()
//**************************************************************************
//CostScalar CostMethodExchange::computeLocalMessageWeight()
//**************************************************************************
CostScalar CostMethodExchange::computeLocalMessageWeight(
const NodeMap *childNodeMap,
const NodeMap *parentNodeMap) const
{
CostScalar numSegments;
if (childNodeMap->getNumEntries() > parentNodeMap->getNumEntries())
{
numSegments = getNumberofSegments(childNodeMap);
}
else
{
numSegments = getNumberofSegments(parentNodeMap);
}
CostScalar localMessageWeight = csOne/numSegments;
return localMessageWeight;
} // CostMethodExchange::computeLocalMessageWeight()
//**************************************************************************
// CostMethodExchange::categorizeMessagesForESP()
//**************************************************************************
void CostMethodExchange::categorizeMessagesForESP(
const PartitioningFunction* parentPartFunc,
const PartitioningFunction* childPartFunc,
const CostScalar &downMessages,
const CostScalar & upMessages,
CostScalar & downIntraCpuMessages,
CostScalar & downIntraSegmentMessages,
CostScalar & downRemoteSegmentMessages,
CostScalar & upIntraCpuMessages,
CostScalar & upIntraSegmentMessages,
CostScalar & upRemoteSegmentMessages) const
{
// check if grouped repartitioning is being attempted
// partitioning keys, types are same
// number of producers/ number of consumers is a power of 2 or
// number of consumers/number of producers is a power of 2
// if that is case, no remote messages
// downward messages get sent every where for an ESP exchange: each lower
// layer ESP process receives same number of requests
const NodeMap* childNodeMap = childPartFunc->getNodeMap();
NABoolean multiSegmentsChild = childNodeMap->isMultiCluster
(0,childNodeMap->getNumEntries(), TRUE);
const NodeMap* parentNodeMap = parentPartFunc->getNodeMap();
NABoolean multiSegmentsParent = parentNodeMap->isMultiCluster
(0, parentNodeMap->getNumEntries(), TRUE);
NABoolean areThereMultiSegments = (multiSegmentsParent || multiSegmentsChild);
// compute local, remote message weight if necessary
CostScalar intraSegmentWeight;
CostScalar intraSegmentFactor = CostScalar(15)/CostScalar(16);
if (areThereMultiSegments)
{
intraSegmentWeight = computeLocalMessageWeight(childNodeMap,
parentNodeMap);
}
NABoolean parentGroupsChildren;
CostScalar paPartsPerGroup;
NABoolean isPAGrouping = isGroupedRepartitioning(
childPartFunc, parentPartFunc,
parentGroupsChildren,
paPartsPerGroup);
// we are not interested in groups of 1 for ESP Exchange
if (paPartsPerGroup == csOne)
isPAGrouping = FALSE;
if ( areThereMultiSegments &&
!isPAGrouping
)
{
downIntraCpuMessages=downMessages * intraSegmentWeight *
(csOne-intraSegmentFactor);
downIntraSegmentMessages=downMessages * intraSegmentWeight*
intraSegmentFactor;
downRemoteSegmentMessages=downMessages-downIntraSegmentMessages -
downIntraCpuMessages;
upIntraCpuMessages = upMessages * intraSegmentWeight *
(csOne-intraSegmentFactor);
upIntraSegmentMessages= upMessages * intraSegmentWeight * intraSegmentFactor;
upRemoteSegmentMessages= upMessages-
upIntraCpuMessages-upIntraSegmentMessages;
}
else if (!areThereMultiSegments && !isPAGrouping)
{
const CostScalar& numOfConsumers = ((NodeMap *)
(parentPartFunc->getNodeMap())
)->getNumActivePartitions();
const CostScalar& numOfPartitions = ((NodeMap *)
(childPartFunc->getNodeMap()))
->getNumActivePartitions();
const CostScalar& numOfProducers = MINOF( numOfPartitions,
sppForChild_->getCurrentCountOfCPUs());
CostScalar outDegree = MAXOF(numOfConsumers, numOfProducers);
upRemoteSegmentMessages=downRemoteSegmentMessages=csZero;
upIntraCpuMessages =upMessages/outDegree;
upIntraSegmentMessages=upMessages -upIntraSegmentMessages;
downIntraCpuMessages = downMessages/outDegree ;
downIntraSegmentMessages=downMessages - downIntraCpuMessages;
}
else // PAGrouping...
{
CostScalar intraCpuMessageWeight=csOne/paPartsPerGroup;
CostScalar remoteSegmentWeight, intraSegmentWeight;
if (paPartsPerGroup <= 16)
{
remoteSegmentWeight = csZero;
intraSegmentWeight = csOne - intraCpuMessageWeight;
}
else
{
remoteSegmentWeight = (paPartsPerGroup - 16)/paPartsPerGroup;
intraSegmentWeight = CostScalar(15) / paPartsPerGroup;
}
downIntraSegmentMessages=downMessages *intraSegmentWeight;
downIntraCpuMessages = downMessages * intraCpuMessageWeight;
downRemoteSegmentMessages=downMessages * remoteSegmentWeight;
upIntraCpuMessages = upMessages * intraCpuMessageWeight;
upIntraSegmentMessages=upMessages * intraSegmentWeight;
upRemoteSegmentMessages= upMessages * remoteSegmentWeight;
}
} // CostMethodExchange::categorizeMessagesForESP()
//<pb>
//==============================================================================
// Determine how many of the specified "down" and "up" messages are internode
// (i.e. cross node boundaries) and how many are intranode (i.e. don't cross
// node boundaries.
//
// Input:
// parentPartFunc -- pointer to parent's partitioning function.
//
// childPartFunc -- pointer to child's partitioning function.
//
// executeInDP2 -- TRUE if child executes in DP2; FALSE otherwise.
//
// downMessages -- number of messages sent from parent down to child.
//
// upMessages -- number of messages sent from child up to parent.
//
// Output:
// downIntraNodeMessages -- number of down messages which do not cross node
// boundaries.
//
// downInterNodeMessages -- number of down messages which cross node
// boundaries.
//
// downRemoteNodeMessages -- number of down messages which cross system boundary
//
// upIntraNodeMessages -- number of up messages which do not cross node
// boundaries.
//
// upInterNodeMessages -- number of up messages which cross node boundaries.
//
// upRemoteNodeMessages -- number of up messages which cross system boundaries
// Return:
// none
//
//==============================================================================
void
CostMethodExchange::categorizeMessages(
const PartitioningFunction* parentPartFunc,
const PartitioningFunction* childPartFunc,
const NABoolean executeInDP2,
const CostScalar & downMessages,
const CostScalar & upMessages,
CostScalar& downIntraNodeMessages,
CostScalar& downInterNodeMessages,
CostScalar& downRemoteNodeMessages,
CostScalar& upIntraNodeMessages,
CostScalar& upInterNodeMessages,
CostScalar& upRemoteNodeMessages) const
{
//----------------------------------------------------------------------
// Defensive programming.
//----------------------------------------------------------------------
CMPASSERT( parentPartFunc != NULL );
CMPASSERT( childPartFunc != NULL );
//----------------------------------------------------------------
// Extract node maps from parent and child partitioning functions
// respectively. Ensure both node maps exist and have the same
// number of entries as their respective partitioning functions.
//----------------------------------------------------------------
const NodeMap* parentNodeMap = parentPartFunc->getNodeMap();
const NodeMap* childNodeMap = childPartFunc->getNodeMap();
CMPASSERT( parentNodeMap != NULL
&& parentPartFunc->getCountOfPartitions()
== (Lng32) parentNodeMap->getNumEntries()
&& childNodeMap != NULL);
//-------------------------------------------------------------------------
// Get addressability to the defaults table.
//-------------------------------------------------------------------------
NADefaults &defs = ActiveSchemaDB()->getDefaults();
//-------------------------------------------------------------------------
// If faked hardware, then set the number of nodes per cluster based on a
// CQD, otherwise get the number of nodes per cluster from gpClusterInfo.
//-------------------------------------------------------------------------
CollIndex numOfNodesInActiveClusters =
( CURRSTMT_OPTDEFAULTS->isFakeHardware() ?
defs.getAsLong(DEF_NUM_NODES_IN_ACTIVE_CLUSTERS)
: gpClusterInfo->numOfSMPs()
);
//---------------------------------------------------------------------------
// The rest of this function may either assume either that grouping is being
// done or that a broadcast is being done. If a grouping is being done, then
// nodeMaps may be used to categorize the types of messages. Broadcasts
// assume that messages may be sent to all nodes and use simple calculations
// based on the system configuration for categorizing the messages.
// Alternatively, if COMP_BOOL_60 is on, then the old behavior of setting
// useNodeMaps based on the "executeInDp2" check will be used.
//
// NOTE: For this build of CQD4, COMP_BOOL_59 must be turned on to to
// use some new code that determines whether grouping is being done. If
// testing shows better plans, then the code that checks COMP_BOOL_59 will
// be modified. If this code is improved to an acceptible point in the
// future, both of these CQDs may be recycled.
//
// There are a few times when grouping is evident. First, when the child
// partitioning function is a LogPhysPartitioningFunction and the logPartType
// is PA_PARTITION_GROUPING or LOGICAL_SUBPARTITIONING. The next is when
// the parent is a grouping of the child. The final case is a reverse
// grouping where the data is being repartitioned from fewer ESPs to more
// ESPs and the child is a logical grouping of the parent. In this case,
// the roles of parent and child are reversed in order to categorize the
// messages in an easy way.
//---------------------------------------------------------------------------
NABoolean useNodeMaps = FALSE;
if (CmpCommon::getDefault(COMP_BOOL_60) == DF_ON)
{
if (executeInDP2)
categorizeMessagesForDP2(parentPartFunc,
childPartFunc,
downMessages,
upMessages,
downIntraNodeMessages,
downInterNodeMessages,
downRemoteNodeMessages,
upIntraNodeMessages,
upInterNodeMessages,
upRemoteNodeMessages) ;
else
categorizeMessagesForESP(parentPartFunc,
childPartFunc,
downMessages,
upMessages,
downIntraNodeMessages,
downInterNodeMessages,
downRemoteNodeMessages,
upIntraNodeMessages,
upInterNodeMessages,
upRemoteNodeMessages) ;
return;
}
// The following code is retained for history reason. The new logic
// (when CB_60 on) is fully tested and will be used all the time.
else if (CmpCommon::getDefault(COMP_BOOL_59) == DF_OFF)
{
useNodeMaps = TRUE;
}
else
{
const LogPhysPartitioningFunction* childLppf =
childPartFunc->castToLogPhysPartitioningFunction();
if (childLppf != NULL
&& (childLppf->getLogPartType() ==
LogPhysPartitioningFunction::PA_PARTITION_GROUPING
|| childLppf->getLogPartType() ==
LogPhysPartitioningFunction::LOGICAL_SUBPARTITIONING))
{
useNodeMaps = TRUE;
}
else if (parentPartFunc->isAGroupingOf(*childPartFunc))
{
useNodeMaps = TRUE;
}
else if (childPartFunc->isAGroupingOf(*parentPartFunc))
{
// This involves repartitioning the data where the child is a grouping
// of the parent. NodeMaps can be used here, but the logic in this
// function must reverse the roles of the parent and child.
parentNodeMap = childPartFunc->getNodeMap();
childNodeMap = parentPartFunc->getNodeMap();
useNodeMaps = TRUE;
}
}
if (useNodeMaps && parentNodeMap->allNodesSpecified()
&& childNodeMap->allNodesSpecified()
&& NOT CURRSTMT_OPTDEFAULTS->isFakeHardware())
{
NABoolean fakeEnv = FALSE;
CollIndex totalEsps = defs.getTotalNumOfESPsInCluster(fakeEnv);
CMPASSERT(parentNodeMap->getNumEntries() <= totalEsps ); //here it needs to be all clusters
//--------------------------------------------------------------------
// Derive the implicit grouping for the parent node map.
//--------------------------------------------------------------------
CollIndexPointer groupStart;
CollIndexPointer groupSize;
((NodeMap*) childNodeMap)->deriveGrouping(parentNodeMap->getNumEntries(),
groupStart,
groupSize);
//----------------------------------------------------------------
// Using the derived grouping, we can now determine the number of
// internode and intranode communication links.
//----------------------------------------------------------------
CostScalar interNode = csZero;
CostScalar intraNode = csZero;
CostScalar remoteNode = csZero;
for (CollIndex parentIdx = 0;
parentIdx < parentNodeMap->getNumEntries();
parentIdx++)
{
//------------------------------------------------------------
// Only count messages that pertain to active parent entries.
//------------------------------------------------------------
if ( parentNodeMap->isActive(parentIdx) )
{
for (CollIndex childIdx = groupStart[parentIdx];
childIdx < groupStart[parentIdx]
+ groupSize[parentIdx];
childIdx++)
{
//-----------------------------------------------------------
// Only count messages that pertain to active child entries.
//-----------------------------------------------------------
if ( childNodeMap->isActive(childIdx) )
{
//------------------------------------------------------
// If parent and child entries are on the same system, we
// have an intrasystem communication link. Otherwise we
// have an intersytem communication link.
//------------------------------------------------------
if(parentNodeMap->getClusterNumber(parentIdx)
!= childNodeMap->getClusterNumber(childIdx) )
{
remoteNode++;
}
//------------------------------------------------------
// If parent and child entries are on the same node, we
// have an intranode communication link. Otherwise we
// have an internode communication link.
//------------------------------------------------------
else if (parentNodeMap->getNodeNumber(parentIdx)
== childNodeMap->getNodeNumber(childIdx) )
{
intraNode++;
}
else
{
interNode++;
}
}
}
}
}
//---------------------------------------------------------------------
// Given the number of intersystem ,internode and intranode communication links,
// we can calculate the percentage of internode and intranode messages.
//---------------------------------------------------------------------
CostScalar interNodePercentage;
CostScalar intraNodePercentage;
CostScalar remoteNodePercentage;
if ((interNode + intraNode + remoteNode).isGreaterThanZero() /*>csZero*/)
{
remoteNodePercentage = remoteNode / (remoteNode+interNode+intraNode);
interNodePercentage = interNode / (remoteNode + interNode + intraNode);
intraNodePercentage = csOne - (interNodePercentage+remoteNodePercentage);
}
else
{
remoteNodePercentage = csZero;
interNodePercentage = csZero;
intraNodePercentage = csZero;
}
//-------------------------------------------------------------------
// Calcualte the number of internode and intranode messages based on
// the internode and intranode percentages calculated above.
//-------------------------------------------------------------------
upRemoteNodeMessages = upMessages * remoteNodePercentage;
upInterNodeMessages = upMessages * interNodePercentage;
upIntraNodeMessages = upMessages * intraNodePercentage;
downRemoteNodeMessages= downMessages * remoteNodePercentage;
downInterNodeMessages = downMessages * interNodePercentage;
downIntraNodeMessages = downMessages * intraNodePercentage;
return;
}
//----------------------------------------------------------------------
// The following code fragment is active if fake hardware is in use or if
// all nodes and clusters are not specified in the parent and child
// node maps.
// Estimate number of active nodes in cluster as the maximum of the
// number of active child nodes and the number of active parent nodes.
// Of course, we can't have more active nodes than actually exist in the
// active clusters.
//----------------------------------------------------------------------
const CollIndex activeChildNodes =
((NodeMap *)childNodeMap)->getEstNumActivePartitionsAtRuntime();
const CollIndex activeParentNodes =
((NodeMap *)parentNodeMap)->getNumActivePartitions();
const CostScalar & activeNodesInClusters =
MINOF( MAXOF( activeChildNodes, activeParentNodes ), numOfNodesInActiveClusters );
CostScalar downInterIntraNodeMessages = csZero;
CostScalar upInterIntraNodeMessages = csZero;
CostScalar activeClusterInNetwork = gpClusterInfo->getNumActiveCluster();
if(CURRSTMT_OPTDEFAULTS->isFakeHardware())
activeClusterInNetwork = csOne;
//-------------------------------------------------------------------------
// Assume all messages are uniformly distributed among all active nodes in
// the cluster.
//-------------------------------------------------------------------------
downInterIntraNodeMessages= downMessages/ activeClusterInNetwork;
downRemoteNodeMessages = downMessages - downInterIntraNodeMessages;
downIntraNodeMessages = downInterIntraNodeMessages / (activeNodesInClusters/activeClusterInNetwork);
downInterNodeMessages = downInterIntraNodeMessages - downIntraNodeMessages;
upInterIntraNodeMessages = upMessages/activeClusterInNetwork;
upRemoteNodeMessages = upMessages - upInterIntraNodeMessages;
upIntraNodeMessages = upInterIntraNodeMessages / (activeNodesInClusters/activeClusterInNetwork);
upInterNodeMessages = upInterIntraNodeMessages - upIntraNodeMessages;
} // CostMethodExchange::categorizeMessages()
//<pb>
//==============================================================================
// Produce cost vectors representing resources used by parent and child of
// Exchange operator to produce their first and last rows.
//
// Input:
// numOfProbes -- number of requests sent from parent to child.
//
// numOfConsumers -- number of parent processes receiving up
// messages and sending down messages.
//
// numOfProducers -- number of child processes receiving down
// messages and sending up messages.
//
// executeInDP2 -- TRUE if child executes in DP2; FALSE otherwise.
//
// myPartFunc -- pointer to my partitioning function.
//
// childPartFunc -- pointer to child's partitioning function.
//
// messageSpacePerRecordInKb -- size of a record (including any header
// overhead) in KB.
//
// messageHeaderInKb -- size of message buffer header in KB.
//
// messageBufferSizeInKb -- size of entire message buffer in KB.
//
// downIntraNodeMessages -- number of down messages which do not cross node
// boundaries.
//
// downInterNodeMessages -- number of down messages which cross node
// boundaries.
//
// downRemoteNodeMessages -- number of down messages which cross system
//. messages
// upIntraNodeMessages -- number of up messages which do not cross node
// boundaries.
//
// upInterNodeMessages -- number of up messages which cross node
// boundaries.
// upRemoteNodeMessages -- number of up messages which cross system boundaries
//
// Output:
// parentFR -- resources used by parent to produce first row.
//
// parentFR -- resources used by parent to produce last row.
//
// childFR -- resources used by child to produce first row.
//
// childFR -- resources used by child to produce last row.
//
// Return:
// none
//
//==============================================================================
void
CostMethodExchange::produceCostVectors(
const CostScalar & numOfProbes,
const CostScalar & numOfConsumers,
const CostScalar & numOfProducers,
const NABoolean childExecutesInDP2,
const PartitioningFunction* myPartFunc,
const PartitioningFunction* childPartFunc,
const CostScalar & messageSpacePerRecordInKb,
const CostScalar & messageHeaderInKb,
const CostScalar & messageBufferSizeInKb,
const CostScalar & upRowsPerConsumer,
const CostScalar & downIntraNodeMessages,
const CostScalar & downInterNodeMessages,
const CostScalar & downRemoteNodeMessages,
const CostScalar & upIntraNodeMessages,
const CostScalar & upInterNodeMessages,
const CostScalar & upRemoteNodeMessages,
CostVecPtr& parentFR,
CostVecPtr& parentLR,
CostVecPtr& childFR,
CostVecPtr& childLR) const
{
if (CmpCommon::getDefault(COMP_BOOL_60) == DF_ON)
{
return produceCostVectorsWithControlDataMessages(
numOfProbes,
numOfConsumers,
numOfProducers,
childExecutesInDP2,
myPartFunc,
childPartFunc,
messageSpacePerRecordInKb,
messageHeaderInKb,
messageBufferSizeInKb,
upRowsPerConsumer_,
downIntraNodeMessages,
downInterNodeMessages,
downRemoteNodeMessages,
upIntraNodeMessages,
upInterNodeMessages,
upRemoteNodeMessages,
parentFR,
parentLR,
childFR,
childLR);
}
//-------------------------------------------------------------------------
// Calculate CPU cost of copying a byte for an Exchange and the additional
// cost of copying that byte across a node or system boundary.
//-------------------------------------------------------------------------
const CostScalar instrPerByte =
CostPrimitives::getBasicCostFactor(CPUCOST_COPY_ROW_PER_BYTE)
+ CostPrimitives::getBasicCostFactor(CPUCOST_EXCHANGE_COST_PER_BYTE);
const CostScalar interNodeInstrPerByte =
CostPrimitives::getBasicCostFactor(CPUCOST_EXCHANGE_INTERNODE_COST_PER_BYTE);
const CostScalar remoteNodeInstrPerByte =
CostPrimitives::getBasicCostFactor(CPUCOST_EXCHANGE_REMOTENODE_COST_PER_BYTE);
//---------------------------------------------------------------------------
// Calculate CPU cost of copying an entire message buffer and the additional
// cost of copying that buffer across a node or system boundary.
//---------------------------------------------------------------------------
const CostScalar instrToCopyAMessage =
( instrPerByte * messageBufferSizeInKb * csOneKiloBytes ).getCeiling();
const CostScalar interNodeInstrToCopyAMessage =
( interNodeInstrPerByte * messageBufferSizeInKb * csOneKiloBytes ).getCeiling();
const CostScalar remoteNodeInstrToCopyAMessage =
( remoteNodeInstrPerByte * messageBufferSizeInKb * csOneKiloBytes ).getCeiling();
//-----------------------------------------------------------------
// There are always two copies made to transfer data from DP2 root
// to the master:
//
// 1.- From DP2InExe buffer to set up message buffer
// (done by root in dp2)
// 2.- From the messaging system to the memory space of the
// master
//
// ESP to ESP communication works in the same way (for now)
//-----------------------------------------------------------------
const CostScalar senderIntraNodeCopies = csOne;
const CostScalar receiverIntraNodeCopies = csOne;
const CostScalar senderInterNodeCopies = csOne;
const CostScalar receiverInterNodeCopies = csOne;
const CostScalar senderRemoteNodeCopies = csOne;
const CostScalar receiverRemoteNodeCopies = csOne;
const CostScalar intraNodeCopiesPerMessage =
senderIntraNodeCopies + receiverIntraNodeCopies;
const CostScalar interNodeCopiesPerMessage =
senderInterNodeCopies + receiverInterNodeCopies;
const CostScalar remoteNodeCopiesPerMessage=
senderRemoteNodeCopies+ receiverRemoteNodeCopies;
//-------------------------------------------------------------
// Distribute the load of messages.
// All messages affect the CPU component.
// Only internode messages affect the LOCAL message component.
// There are no intercluster (i.e. REMOTE) messages on NT.
// Note: Intra-node (i.e. intra-cpu on NSK) messages are
// ignored when computing the LOCAL message component. Such
// messages merely involve a memory-to-memory copy.- Sunil
//--------------------------------------------------------
//------------------------------------------------------------------
// Compute number of copies for intra node, internode and remote
// messages from the parent perspective.
//------------------------------------------------------------------
CostScalar parentIntraNodeCopies =
(downIntraNodeMessages * senderIntraNodeCopies)
+ (upIntraNodeMessages * receiverIntraNodeCopies);
CostScalar parentInterNodeCopies =
(downInterNodeMessages * senderInterNodeCopies)
+ (upInterNodeMessages * receiverInterNodeCopies);
CostScalar parentRemoteNodeCopies =
(downRemoteNodeMessages * senderRemoteNodeCopies)
+ (upRemoteNodeMessages * receiverRemoteNodeCopies);
// Divide total IntraNode messages by number of intra nodes.
// Divide total InterNode messages by number of inter nodes.
// Divide total RemoteNode messages by number of remote nodes.
CostScalar intraNode = csZero;
CostScalar interNode = csZero;
CostScalar remoteNode = csZero;
CostScalar downMessages = downIntraNodeMessages + downInterNodeMessages
+ downRemoteNodeMessages;
if ( CmpCommon::getDefault(COMP_BOOL_97) == DF_OFF )
{
intraNode = (downIntraNodeMessages/downMessages) * numOfConsumers;
interNode = (downInterNodeMessages/downMessages) * numOfConsumers;
remoteNode = (downRemoteNodeMessages/downMessages) * numOfConsumers;
}
else
{
intraNode = interNode = remoteNode = numOfConsumers;
}
//----------------------------------------------------------------
// We have calculated the total number of messages, now normalize
// these to the number of parent's CPUs (numOfConsumers).
// Note: What if we have 2 consuming ESPs per CPU (i.e. MAX_ESPS_PER_CPU_PER_OP=2)?
// If so, then numOfConsumers <> no. of CPUs.-- Sunil
//----------------------------------------------------------------
CostScalar parentIntraNodeCopiesPerCPU = parentIntraNodeCopies
/ MIN_ONE_CS(intraNode);
CostScalar parentInterNodeCopiesPerCPU = parentInterNodeCopies
/ MIN_ONE_CS(interNode);
CostScalar parentRemoteNodeCopiesPerCPU = parentRemoteNodeCopies
/ MIN_ONE_CS(remoteNode);
CostScalar downInterNodeMessagesPerCPU = downInterNodeMessages
/ MIN_ONE_CS(interNode);
CostScalar downRemoteNodeMessagesPerCPU = downRemoteNodeMessages
/ MIN_ONE_CS(remoteNode);
//-------------------------------------------------------
// Calculate number of bytes of down internode and intersystem messages.
//-------------------------------------------------------
CostScalar parentInterNodeMessagesInKbPerCPU = downInterNodeMessagesPerCPU
* messageBufferSizeInKb;
CostScalar parentRemoteNodeMessagesInKbPerCPU= downRemoteNodeMessagesPerCPU
* messageBufferSizeInKb;
//----------------------------------------------------------------
// Calculate number of total message copies and internode message
// copies for producing the first row.
//----------------------------------------------------------------
const CostScalar & firstRowParentIntraNodeCopiesPerCPU =
MINOF( parentIntraNodeCopiesPerCPU, intraNodeCopiesPerMessage );
const CostScalar & firstRowParentInterNodeCopiesPerCPU =
MINOF( parentInterNodeCopiesPerCPU, interNodeCopiesPerMessage );
const CostScalar & firstRowParentRemoteNodeCopiesPerCPU =
MINOF(parentRemoteNodeCopiesPerCPU,
remoteNodeCopiesPerMessage);
//----------------------------------------------------------------------------
// Calculate memory usage. We need enough memory for each copy of a message.
// When the child does not executes in DP2, each producer needs enough memory
// for each copy of a message.
//----------------------------------------------------------------------------
CostScalar parentMemory = messageBufferSizeInKb * (MINOF(parentInterNodeCopiesPerCPU,
interNodeCopiesPerMessage)+
MINOF(parentIntraNodeCopiesPerCPU,
intraNodeCopiesPerMessage)+
MINOF(parentRemoteNodeCopiesPerCPU,
remoteNodeCopiesPerMessage));
if (NOT childExecutesInDP2)
{
parentMemory = parentMemory * numOfProducers;
}
//-----------------
// Parent First Row
//-----------------
parentFR = new STMTHEAP SimpleCostVector;
parentFR->setInstrToCPUTime( firstRowParentIntraNodeCopiesPerCPU
* instrToCopyAMessage );
parentFR->addNumLocalToMSGTime(
MINOF( csOne, downInterNodeMessagesPerCPU ) );
parentFR->addKBLocalToMSGTime(
MINOF( messageBufferSizeInKb,
parentInterNodeMessagesInKbPerCPU ) );
parentFR->addNumRemoteToMSGTime(
MINOF(csOne,downRemoteNodeMessagesPerCPU));
parentFR->addKBRemoteToMSGTime(
MINOF(messageBufferSizeInKb,
parentRemoteNodeMessagesInKbPerCPU));
//parentFR->setNormalMemory(parentMemory);
parentFR->setNumProbes(numOfProbes);
//-----------------
// Parent Last Row
//-----------------
parentLR = new STMTHEAP SimpleCostVector;
parentLR->setInstrToCPUTime( ( parentIntraNodeCopiesPerCPU
* instrToCopyAMessage)
+ ( parentInterNodeCopiesPerCPU
* interNodeInstrToCopyAMessage)
+ ( parentRemoteNodeCopiesPerCPU
* remoteNodeInstrToCopyAMessage));
parentLR->addNumLocalToMSGTime( downInterNodeMessagesPerCPU) ;
parentLR->addKBLocalToMSGTime( parentInterNodeMessagesInKbPerCPU );
parentLR->addNumRemoteToMSGTime( downRemoteNodeMessagesPerCPU );
parentLR->addKBRemoteToMSGTime( parentRemoteNodeMessagesInKbPerCPU );
// parentLR->setNormalMemory( parentMemory );
parentLR->setNumProbes( numOfProbes );
// give some weight for parallel plans compared to serial plan when
// a large number of rows are being returned to application
NADefaults &defs1 = ActiveSchemaDB()->getDefaults();
CostScalar adj1 = defs1.getAsLong(COMP_INT_62);
if (adj1 == csZero)
adj1=1000000;
NABoolean adjustmentForParallelPlans = FALSE;
if ( (numOfConsumers == csOne) AND
(numOfProducers > numOfConsumers) AND
isOpBelowRoot_ AND
upRowsPerConsumer_ > adj1 )
{
parentLR->setToValue(0.0001) ;
parentFR->setToValue(0.0001) ;
adjustmentForParallelPlans = TRUE;
}
//------------------------------------------------------------------
// Compute number of copies for intra node and for just internode
// messages from child's perspective.
//------------------------------------------------------------------
CostScalar childIntraNodeCopies =
(downIntraNodeMessages * receiverIntraNodeCopies)
+ (upIntraNodeMessages * senderIntraNodeCopies);
CostScalar childInterNodeCopies =
(downInterNodeMessages * receiverInterNodeCopies)
+ (upInterNodeMessages * senderInterNodeCopies);
CostScalar childRemoteNodeCopies =
(downRemoteNodeMessages* receiverInterNodeCopies)
+ (upRemoteNodeMessages * senderRemoteNodeCopies);
// Divide total IntraNode messages by number of intra nodes.
// Divide total InterNode messages by number of inter nodes.
// Divide total RemoteNode messages by number of remote nodes.
CostScalar upMessages = upIntraNodeMessages + upInterNodeMessages
+ upRemoteNodeMessages;
if ( CmpCommon::getDefault(COMP_BOOL_97) == DF_OFF )
{
intraNode = (upIntraNodeMessages/upMessages) * numOfProducers;
interNode = (upInterNodeMessages/upMessages) * numOfProducers;
remoteNode = (upRemoteNodeMessages/upMessages) * numOfProducers;
}
else
{
intraNode = interNode = remoteNode = numOfProducers;
}
//-------------------------------------------------------------------------
// The messages used in calculating childIntraNodeCopiesPerCPU and
// upInterNodeMessagesPerCPU,upRemoteNodeMessagesPerCPU were based on the
// MAXOF producers and senders. Normalize this to the number of CPUs in the child.
//-------------------------------------------------------------------------
CostScalar childIntraNodeCopiesPerCPU = childIntraNodeCopies
/ MIN_ONE_CS(intraNode);
CostScalar childInterNodeCopiesPerCPU = childInterNodeCopies
/ MIN_ONE_CS(interNode);
CostScalar childRemoteNodeCopiesPerCPU = childRemoteNodeCopies
/ MIN_ONE_CS(remoteNode);
CostScalar upInterNodeMessagesPerCPU = upInterNodeMessages
/ MIN_ONE_CS(interNode);
CostScalar upRemoteNodeMessagesPerCPU = upRemoteNodeMessages
/ MIN_ONE_CS(remoteNode);
CostScalar childInterNodeMessagesInKbPerCPU = upInterNodeMessagesPerCPU
* messageBufferSizeInKb;
CostScalar childRemoteNodeMessagesInKbPerCPU= upRemoteNodeMessagesPerCPU
* messageBufferSizeInKb;
const CostScalar & firstRowChildIntraNodeCopiesPerCPU =
MINOF( childIntraNodeCopiesPerCPU, intraNodeCopiesPerMessage );
//----------------------------------------------------------------------------
// Calculate memory usage. We need enough memory for each copy of a message.
// When the child does not execute in DP2, each consumer needs enough memory
// for each copy of a message.
//----------------------------------------------------------------------------
CostScalar childMemory = messageBufferSizeInKb * (interNodeCopiesPerMessage
+ remoteNodeCopiesPerMessage);
if ( NOT childExecutesInDP2 )
{
childMemory = childMemory * numOfConsumers;
}
//------------------------------------------------------------------------------
// Cost to compute the hash value for a row.
//
// I have made 2 changes: The hash value needs to be computed only if range or
// hash repartitioning is taking place. And this determination needs to be made
// based on the Exchange's (and not child's) partitioning function. Note that we
// ignore the cpu costs associated with round-robin and random repartitioning;
// these costs are trivial. Range-repartitioning involves evaluating the range
// partitioning function for each row. This requires a binary search through
// the partition key arrary, using an encoded key (derived from the row) as the
// search key. In lieu of a cost estimate for this binary search, we'll use the
// CPU cost for computing a hash partitioning function. - Sunil
//-------------------------------------------------------------------------------
CostScalar cpuCostHashRow = csZero;
if ( NOT childExecutesInDP2 AND numOfConsumers.isGreaterThanOne() /*> csOne*/
AND ( myPartFunc->isAHashPartitioningFunction()
OR myPartFunc->isAHash2PartitioningFunction()
OR myPartFunc->isAHashDistPartitioningFunction()
OR myPartFunc->isARangePartitioningFunction()
OR myPartFunc->isASkewedDataPartitioningFunction()
))
{
if ( CmpCommon::getDefault(COMP_BOOL_97) == DF_OFF )
cpuCostHashRow =
CostPrimitives::cpuCostForHash( myPartFunc->getPartitioningKey() );
else
cpuCostHashRow =
CostPrimitives::cpuCostForHash( childPartFunc->getPartitioningKey() );
}
// if the exchange is PAPA and for Hash2 and Hash1 pfs, we need to
// cost hashing of incoming probes...
if ( (CmpCommon::getDefault(COMP_BOOL_57) == DF_OFF) AND
childExecutesInDP2 AND
(numOfProducers> numOfConsumers) AND
childPartFunc->isALogPhysPartitioningFunction() AND
numOfProbes.isGreaterThanOne()
)
{
PartitioningFunction* phys = childPartFunc->
castToLogPhysPartitioningFunction()->
getPhysPartitioningFunction();
// Hash2 and Hash1 partitioning functions have different functions
// to compute partition numbers and hash1 is more expensive
// that should be reflected in the cost computation
if (phys->isAHash2PartitioningFunction() OR
phys->isAHashDistPartitioningFunction() OR
phys->isARangePartitioningFunction())
{
Exchange *exch= (Exchange *) op_;
NABoolean areProbesHashed=
exch->areProbesHashed(childPartFunc->getPartitioningKey());
if (areProbesHashed)
{
CostScalar cpuCostHashProbes =
CostPrimitives::cpuCostForHash( childPartFunc->getPartitioningKey() );
parentLR->addInstrToCPUTime( cpuCostHashProbes *
(numOfProbes/numOfConsumers).minCsOne());
}
}
}
if ( (CmpCommon::getDefault(COMP_BOOL_57) == DF_OFF) AND
isMergeNeeded_ AND
numOfProducers> numOfConsumers )
{
CostScalar cpuCostCompareKeys =
CostPrimitives::cpuCostForCompare(sppForMe_->getSortKey());
parentLR->addInstrToCPUTime( cpuCostCompareKeys *
upRowsPerConsumer_);
}
//----------------------------------------------------------------------------
// Compute maximum number of rows that can fit in a single message buffer and
// make this the upper bound for the number of messages associated with the
// child's first row cost.
//----------------------------------------------------------------------------
const CostScalar maxRowsPerMessage =
( ( messageBufferSizeInKb - messageHeaderInKb ) / messageSpacePerRecordInKb
).getFloor();
const CostScalar & firstRowNumOfRows =
MINOF( upRowsPerConsumer, maxRowsPerMessage );
//----------------
// Child First Row
//----------------
childFR = new STMTHEAP SimpleCostVector;
childFR->setInstrToCPUTime( ( firstRowChildIntraNodeCopiesPerCPU
* instrToCopyAMessage)
+ (firstRowNumOfRows * cpuCostHashRow));
childFR->addNumLocalToMSGTime( MINOF( csOne, upInterNodeMessagesPerCPU ) );
childFR->addKBLocalToMSGTime(
MINOF(messageBufferSizeInKb,
childInterNodeMessagesInKbPerCPU));
childFR->addNumRemoteToMSGTime(
MINOF(csOne,upRemoteNodeMessagesPerCPU));
childFR->addKBRemoteToMSGTime(
MINOF(messageBufferSizeInKb,
childRemoteNodeMessagesInKbPerCPU));
// childFR->setNormalMemory( childMemory );
childFR->setNumProbes( numOfProbes );
//---------------
// Child Last Row
//---------------
childLR = new STMTHEAP SimpleCostVector;
childLR->setInstrToCPUTime( (childIntraNodeCopiesPerCPU
* instrToCopyAMessage)
+ ( childInterNodeCopiesPerCPU
* interNodeInstrToCopyAMessage)
+ ( childRemoteNodeCopiesPerCPU
* remoteNodeInstrToCopyAMessage)
+ (upRowsPerConsumer * cpuCostHashRow));
childLR->addNumLocalToMSGTime( upInterNodeMessagesPerCPU );
childLR->addKBLocalToMSGTime( childInterNodeMessagesInKbPerCPU );
childLR->addNumRemoteToMSGTime( upRemoteNodeMessagesPerCPU );
childLR->addKBRemoteToMSGTime( childRemoteNodeMessagesInKbPerCPU );
// childLR->setNormalMemory( childMemory );
childLR->setNumProbes( numOfProbes );
if (adjustmentForParallelPlans)
{
childFR->scaleByValue(csZero);
childLR->scaleByValue(csZero);
}
//----------------------------------------------------------------
// Compute the cost of starting ESPs as an idle time cost. Store
// this idle time in both the first row and last row vectors.
//----------------------------------------------------------------
//SimpleCostVector* espStartupCost = computeESPCost(NOT executeInDP2,
// numOfProbes);
//CostScalar espElapsedTime = espStartupCost->getCPUTime();
// + espStartupCost->getIOTime();
// computeEXPCost has been simplified by removing IO component. Nov.2005
CostScalar espElapsedTime = computeESPCost(NOT childExecutesInDP2,numOfProbes);
// ESP startup cost is waited, so must pay the price of ESP startup
// for each consumer, according to Hans (4/20/99).
espElapsedTime *= numOfConsumers;
CostScalar startupAdj = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(COMP_INT_63);
if( isOpBelowRoot_ && (NOT childExecutesInDP2) && ( numOfProbes < 10 ) && (CmpCommon::getDefault(COMP_BOOL_122) == DF_ON) ){
parentFR->addToCpuTime( (espElapsedTime * startupAdj) / numOfProbes );
parentLR->addToCpuTime( (espElapsedTime * startupAdj) / numOfProbes );
}
} // CostMethodExchange::produceCostVectors()
void
CostMethodExchange::produceCostVectorsWithControlDataMessages(
const CostScalar & numOfProbes,
const CostScalar & numOfConsumers,
const CostScalar & numOfProducers,
const NABoolean childExecutesInDP2,
const PartitioningFunction* myPartFunc,
const PartitioningFunction* childPartFunc,
const CostScalar & messageSpacePerRecordInKb,
const CostScalar & messageHeaderInKb,
const CostScalar & messageBufferSizeInKb,
const CostScalar & upRowsPerConsumer,
const CostScalar & downIntraCpuMessages,
const CostScalar & downIntraSegmentMessages,
const CostScalar & downRemoteSegmentMessages,
const CostScalar & upIntraCPUMessages,
const CostScalar & upIntraSegmentMessages,
const CostScalar & upRemoteSegmentMessages,
CostVecPtr& parentFR,
CostVecPtr& parentLR,
CostVecPtr& childFR,
CostVecPtr& childLR) const
{
//-------------------------------------------------------------------------
// Calculate CPU cost of copying a byte for an Exchange and the additional
// cost of copying that byte across a node or system boundary.
//-------------------------------------------------------------------------
const CostScalar instrPerByte =
CostPrimitives::getBasicCostFactor(CPUCOST_COPY_ROW_PER_BYTE)
+ CostPrimitives::getBasicCostFactor(CPUCOST_EXCHANGE_COST_PER_BYTE);
const CostScalar intraSegmentInstrPerByte =
CostPrimitives::getBasicCostFactor(CPUCOST_EXCHANGE_INTERNODE_COST_PER_BYTE);
const CostScalar remoteSegmentInstrPerByte =
CostPrimitives::getBasicCostFactor(CPUCOST_EXCHANGE_REMOTENODE_COST_PER_BYTE);
//---------------------------------------------------------------------------
// Calculate CPU cost of copying an entire message buffer and the additional
// cost of copying that buffer across a node or system boundary.
//---------------------------------------------------------------------------
const CostScalar instrToCopyA1KBMessage =
( instrPerByte * csOneKiloBytes ).getCeiling();
const CostScalar intraSegmentInstrToCopyA1KBMessage =
( intraSegmentInstrPerByte * csOneKiloBytes ).getCeiling();
const CostScalar remoteSegmentInstrToCopyA1KBMessage =
( remoteSegmentInstrPerByte * csOneKiloBytes ).getCeiling();
CostScalar downMessages = downIntraCpuMessages +
downRemoteSegmentMessages +
downIntraSegmentMessages;
// these message lengths are in kilo bytes
CostScalar downIntraSegmentMessagesLength;
CostScalar downRemoteSegmentMessagesLength;
CostScalar downIntraCpuMessagesLength;
// downMessageLength_ is in bytes; so we divide by csOneKiloBytes
CostScalar normalizeFactor = downMessages * csOneKiloBytes;
downIntraSegmentMessagesLength = downMessageLength_ * downIntraSegmentMessages
/ normalizeFactor;
downRemoteSegmentMessagesLength = downMessageLength_ * downRemoteSegmentMessages
/ normalizeFactor ;
downIntraCpuMessagesLength = downMessageLength_ * downIntraCpuMessages
/ normalizeFactor;
/*else
{
// downMessageLength_ is already in kilo-bytes
CostScalar normalizeFactor = downMessageLength_/downMessages;
downIntraSegmentMessagesLength = downIntraSegmentMessages*normalizeFactor;
downRemoteSegmentMessagesLength = downRemoteSegmentMessages*normalizeFactor;
downIntraCpuMessagesLength = downIntraCpuMessages*normalizeFactor;
}*/
//-----------------------------------------------------------------
// There are always two copies made to transfer data from DP2 root
// to the master:
//
// 1.- From DP2InExe buffer to set up message buffer
// (done by root in dp2)
// 2.- From the messaging system to the memory space of the
// master
//
// ESP to ESP communication works in the same way (for now)
//-----------------------------------------------------------------
const CostScalar senderIntraCPUCopies = csOne;
const CostScalar receiverIntraCPUCopies = csOne;
const CostScalar senderIntraSegmentCopies = csOne;
const CostScalar receiverIntraSegmentCopies = csOne;
const CostScalar senderRemoteSegmentCopies = csOne;
const CostScalar receiverRemoteSegmentCopies = csOne;
const CostScalar intraCPUCopiesPerMessage =
senderIntraCPUCopies + receiverIntraCPUCopies;
const CostScalar interSegmentCopiesPerMessage =
senderIntraSegmentCopies + receiverIntraSegmentCopies;
const CostScalar remoteSegmentCopiesPerMessage=
senderRemoteSegmentCopies+ receiverRemoteSegmentCopies;
//-------------------------------------------------------------
// Distribute the load of messages.
// All messages affect the CPU component.
// Only internode messages affect the LOCAL message component.
// There are no intercluster (i.e. REMOTE) messages on NT.
// Note: Intra-node (i.e. intra-cpu on NSK) messages are
// ignored when computing the LOCAL message component. Such
// messages merely involve a memory-to-memory copy.- Sunil
//--------------------------------------------------------
//------------------------------------------------------------------
// Compute number of copies for intra node, internode and remote
// messages from the parent perspective. all messages are normaized to
// 1 Kb even though up and down buffer sizes could be different
// This is used in computing COPY cost only.
//------------------------------------------------------------------
CostScalar parentIntraCPUCopyMessageLength =
(downIntraCpuMessagesLength * senderIntraCPUCopies)
+ (upIntraCPUMessages * upMessageBufferLength_ *
receiverIntraCPUCopies);
CostScalar parentIntraSegmentCopyMessageLength =
(downIntraSegmentMessagesLength * senderIntraSegmentCopies)
+ (upIntraSegmentMessages * upMessageBufferLength_ *
receiverIntraSegmentCopies);
CostScalar parentRemoteSegmentCopyMessageLength =
(downRemoteSegmentMessagesLength * senderRemoteSegmentCopies)
+ (upRemoteSegmentMessages * upMessageBufferLength_ *
receiverRemoteSegmentCopies);
// Divide total IntraNode messages by number of intra nodes.
// Divide total InterNode messages by number of inter nodes.
// Divide total RemoteNode messages by number of remote nodes.
CostScalar intraNode = csZero;
CostScalar interNode = csZero;
CostScalar remoteNode = csZero;
if ( CmpCommon::getDefault(COMP_BOOL_97) == DF_ON )
{
intraNode = (downIntraCpuMessages/downMessages) * numOfConsumers;
interNode = (downIntraSegmentMessages/downMessages) * numOfConsumers;
remoteNode = (downRemoteSegmentMessages/downMessages) * numOfConsumers;
}
else
{
intraNode = interNode = remoteNode = numOfConsumers;
}
//----------------------------------------------------------------
// We have calculated the total number of messages, now normalize
// these to the number of parent's CPUs (numOfConsumers).
// Note: What if we have 2 consuming ESPs per CPU (i.e. MAX_ESPS_PER_CPU_PER_OP=2)?
// If so, then numOfConsumers <> no. of CPUs.-- Sunil
//----------------------------------------------------------------
CostScalar parentIntraCPUCopyMessageLengthPerConsumer =
parentIntraCPUCopyMessageLength / intraNode.minCsOne();
CostScalar parentIntraSegmentCopyLengthPerConsumer =
parentIntraSegmentCopyMessageLength / interNode.minCsOne();
CostScalar parentRemoteSegmentCopyLengthPerConsumer =
parentRemoteSegmentCopyMessageLength / remoteNode.minCsOne();
CostScalar downIntraSegmentMessagesPerConsumer = downIntraSegmentMessages
/ interNode.minCsOne();
CostScalar downRemoteSegmentMessagesPerConsumer = downRemoteSegmentMessages
/ remoteNode.minCsOne();
//-------------------------------------------------------
// Calculate number of kilobytes of down internode and intersegment
// messages. They are normalized
//-------------------------------------------------------
CostScalar parentIntraSegmentMessagesInKbPerConsumer =
downIntraSegmentMessagesLength / interNode.minCsOne();
CostScalar parentRemoteSegmentMessagesInKbPerConsumer=
downRemoteSegmentMessagesLength / remoteNode.minCsOne();
//----------------------------------------------------------------
// Calculate number of total message copies and internode message
// copies for producing the first row.
//----------------------------------------------------------------
const CostScalar & firstRowParentIntraNodeCopiesPerConsumer =
MINOF( parentIntraCPUCopyMessageLengthPerConsumer, intraCPUCopiesPerMessage );
const CostScalar & firstRowParentInterNodeCopiesPerConsumer =
MINOF( parentIntraSegmentCopyLengthPerConsumer, interSegmentCopiesPerMessage );
const CostScalar & firstRowParentRemoteNodeCopiesPerConsumer =
MINOF(parentRemoteSegmentCopyLengthPerConsumer, remoteSegmentCopiesPerMessage);
//----------------------------------------------------------------------------
// Calculate memory usage. We need enough memory for each copy of a message.
// When the child does not executes in DP2, each producer needs enough memory
// for each copy of a message.
//----------------------------------------------------------------------------
/*CostScalar parentMemory = messageBufferSizeInKb * (MINOF(parentInterNodeCopiesPerCPU,
interSegmentCopiesPerMessage)+
MINOF(parentIntraNodeCopiesPerCPU,
intraCPUCopiesPerMessage)+
MINOF(parentRemoteNodeCopiesPerCPU,
remoteSegmentCopiesPerMessage));
if (NOT childExecutesInDP2)
{
parentMemory = parentMemory * numOfProducers;
}*/
//-----------------
// Parent First Row
//-----------------
parentFR = new STMTHEAP SimpleCostVector;
parentFR->setInstrToCPUTime( firstRowParentIntraNodeCopiesPerConsumer
* instrToCopyA1KBMessage );
parentFR->addNumLocalToMSGTime(
MINOF( csOne, downIntraSegmentMessagesPerConsumer ) );
parentFR->addKBLocalToMSGTime(
MINOF( downMessageBufferLength_,
parentIntraSegmentMessagesInKbPerConsumer ) );
parentFR->addNumRemoteToMSGTime(
MINOF(csOne,downRemoteSegmentMessagesPerConsumer));
parentFR->addKBRemoteToMSGTime(
MINOF(downMessageBufferLength_,
parentRemoteSegmentMessagesInKbPerConsumer));
//parentFR->setNormalMemory(parentMemory);
parentFR->setNumProbes(numOfProbes);
//-----------------
// Parent Last Row
//-----------------
parentLR = new STMTHEAP SimpleCostVector;
CostScalar copyCost =
parentIntraCPUCopyMessageLengthPerConsumer * instrToCopyA1KBMessage +
parentIntraSegmentCopyLengthPerConsumer * intraSegmentInstrToCopyA1KBMessage +
parentRemoteSegmentCopyLengthPerConsumer * intraSegmentInstrToCopyA1KBMessage;
// this used to be remoteSegmentInstrToCopyA1KBMessage;
// may need some calibration work
parentLR->setInstrToCPUTime(copyCost);
parentLR->addNumLocalToMSGTime( downIntraSegmentMessagesPerConsumer) ;
parentLR->addKBLocalToMSGTime( parentIntraSegmentMessagesInKbPerConsumer );
parentLR->addNumRemoteToMSGTime( downRemoteSegmentMessagesPerConsumer );
parentLR->addKBRemoteToMSGTime(parentRemoteSegmentMessagesInKbPerConsumer);
// parentLR->setNormalMemory( parentMemory );
parentLR->setNumProbes( numOfProbes );
// give some weight for parallel plans compared to serial plan when
// a large number of rows are being returned to application
NADefaults &defs1 = ActiveSchemaDB()->getDefaults();
CostScalar adj1 = defs1.getAsLong(COMP_INT_62);
if (adj1 == csZero)
adj1=1000000;
NABoolean adjustmentForParallelPlans = FALSE;
if ( (numOfConsumers == csOne) AND
(numOfProducers > numOfConsumers) AND
isOpBelowRoot_ AND
upRowsPerConsumer_ > adj1 )
{
parentLR->setToValue(0.0001) ;
parentFR->setToValue(0.0001) ;
adjustmentForParallelPlans = TRUE;
}
//------------------------------------------------------------------
// Messages Length in bytes at the child layer
//
//------------------------------------------------------------------
CostScalar childIntraCPUCopyMessageLength =
(downIntraCpuMessagesLength * receiverIntraCPUCopies)
+ (upIntraCPUMessages * upMessageBufferLength_ *
senderIntraCPUCopies);
CostScalar childIntraSegmentCopyMessageLength =
(downIntraSegmentMessagesLength * receiverIntraSegmentCopies)
+ (upIntraSegmentMessages * upMessageBufferLength_ *
senderIntraSegmentCopies);
CostScalar childRemoteSegmentCopyMessageLength =
(downRemoteSegmentMessagesLength * receiverIntraSegmentCopies)
+ (upRemoteSegmentMessages * upMessageBufferLength_ *
senderRemoteSegmentCopies);
// Divide total IntraNode messages by number of intra nodes.
// Divide total InterNode messages by number of inter nodes.
// Divide total RemoteNode messages by number of remote nodes.
CostScalar upMessages = upIntraCPUMessages + upIntraSegmentMessages
+ upRemoteSegmentMessages;
if ( CmpCommon::getDefault(COMP_BOOL_97) == DF_ON )
{
intraNode = (upIntraCPUMessages/upMessages) * numOfProducers;
interNode = (upIntraSegmentMessages/upMessages) * numOfProducers;
remoteNode = (upRemoteSegmentMessages/upMessages) * numOfProducers;
}
else
{
intraNode = interNode = remoteNode = numOfProducers;
}
//-------------------------------------------------------------------------
// The messages used in calculating childIntraNodeCopiesPerCPU and
// upIntraSegmentMessagesPerCPU,upRemoteSegmentMessagesPerCPU were based on the
// MAXOF producers and senders. Normalize this to the number of CPUs in the child.
//-------------------------------------------------------------------------
CostScalar childIntraCPUCopyLengthPerProducer =
childIntraCPUCopyMessageLength / intraNode.minCsOne();
CostScalar childIntraSegmentCopyLengthPerProducer =
childIntraSegmentCopyMessageLength / interNode.minCsOne();
CostScalar childRemoteSegmentCopyLengthPerProducer =
childRemoteSegmentCopyMessageLength / remoteNode.minCsOne();
CostScalar childUpIntraSegmentMessagesPerProducer =
upIntraSegmentMessages / interNode.minCsOne();
CostScalar childUpRemoteSegmentMessagesPerProducer =
upRemoteSegmentMessages / remoteNode.minCsOne();
CostScalar childIntraSegmentMessagesInKbPerProducer =
upIntraSegmentMessages * upMessageBufferLength_/interNode.minCsOne();
CostScalar childRemoteSegmentMessagesInKbPerProducer=
upRemoteSegmentMessages * upMessageBufferLength_ / remoteNode.minCsOne();
const CostScalar & firstRowChildIntraNodeCopiesPerProducer =
MINOF( childIntraCPUCopyLengthPerProducer, childIntraCPUCopyMessageLength);
//----------------------------------------------------------------------------
// Calculate memory usage. We need enough memory for each copy of a message.
// When the child does not execute in DP2, each consumer needs enough memory
// for each copy of a message.
//----------------------------------------------------------------------------
CostScalar childMemory = messageBufferSizeInKb * (interSegmentCopiesPerMessage
+ remoteSegmentCopiesPerMessage);
if ( NOT childExecutesInDP2 )
{
childMemory = childMemory * numOfConsumers;
}
//------------------------------------------------------------------------------
// Cost to compute the hash value for a row.
//
// I have made 2 changes: The hash value needs to be computed only if range or
// hash repartitioning is taking place. And this determination needs to be made
// based on the Exchange's (and not child's) partitioning function. Note that we
// ignore the cpu costs associated with round-robin and random repartitioning;
// these costs are trivial. Range-repartitioning involves evaluating the range
// partitioning function for each row. This requires a binary search through
// the partition key arrary, using an encoded key (derived from the row) as the
// search key. In lieu of a cost estimate for this binary search, we'll use the
// CPU cost for computing a hash partitioning function. - Sunil
//-------------------------------------------------------------------------------
CostScalar cpuCostHashRow = csZero;
if ( NOT childExecutesInDP2 AND numOfConsumers.isGreaterThanOne() /*> csOne*/
AND ( myPartFunc->isAHashPartitioningFunction()
OR myPartFunc->isAHash2PartitioningFunction()
OR myPartFunc->isAHashDistPartitioningFunction()
OR myPartFunc->isARangePartitioningFunction()
OR myPartFunc->isASkewedDataPartitioningFunction()
))
{
if ( CmpCommon::getDefault(COMP_BOOL_97) == DF_OFF )
cpuCostHashRow =
CostPrimitives::cpuCostForHash( myPartFunc->getPartitioningKey() );
else
cpuCostHashRow =
CostPrimitives::cpuCostForHash( childPartFunc->getPartitioningKey() );
}
// if the exchange is PAPA and for Hash2 and Hash1 pfs, we need to
// cost hashing of incoming probes...
if ( (CmpCommon::getDefault(COMP_BOOL_57) == DF_OFF) AND
childExecutesInDP2 AND
(numOfProducers> numOfConsumers) AND
childPartFunc->isALogPhysPartitioningFunction() AND
numOfProbes.isGreaterThanOne()
)
{
PartitioningFunction* phys = childPartFunc->
castToLogPhysPartitioningFunction()->
getPhysPartitioningFunction();
// Hash2 and Hash1 partitioning functions have different functions
// to compute partition numbers and hash1 is more expensive
// that should be reflected in the cost computation
if (phys->isAHash2PartitioningFunction() OR
phys->isAHashDistPartitioningFunction() OR
phys->isARangePartitioningFunction())
{
Exchange *exch= (Exchange *) op_;
NABoolean areProbesHashed=
exch->areProbesHashed(childPartFunc->getPartitioningKey());
if (areProbesHashed)
{
CostScalar cpuCostHashProbes =
CostPrimitives::cpuCostForHash( childPartFunc->getPartitioningKey() );
parentLR->addInstrToCPUTime( cpuCostHashProbes *
(numOfProbes/numOfConsumers).minCsOne());
}
}
}
if ( (CmpCommon::getDefault(COMP_BOOL_57) == DF_OFF) AND
isMergeNeeded_ AND
numOfProducers> numOfConsumers )
{
CostScalar cpuCostCompareKeys =
CostPrimitives::cpuCostForCompare(sppForMe_->getSortKey());
parentLR->addInstrToCPUTime( cpuCostCompareKeys *
upRowsPerConsumer_);
}
//----------------------------------------------------------------------------
// Compute maximum number of rows that can fit in a single message buffer and
// make this the upper bound for the number of messages associated with the
// child's first row cost.
//----------------------------------------------------------------------------
const CostScalar maxRowsPerMessage =
( ( upMessageBufferLength_ - messageHeaderInKb )
/ messageSpacePerRecordInKb).getFloor();
const CostScalar & firstRowNumOfRows =
MINOF( upRowsPerConsumer, maxRowsPerMessage );
//----------------
// Child First Row
//----------------
childFR = new STMTHEAP SimpleCostVector;
childFR->setInstrToCPUTime(
firstRowChildIntraNodeCopiesPerProducer
* instrToCopyA1KBMessage
+ (firstRowNumOfRows * cpuCostHashRow));
childFR->addNumLocalToMSGTime(
MINOF( csOne, childUpIntraSegmentMessagesPerProducer )
);
childFR->addKBLocalToMSGTime(
MINOF(messageBufferSizeInKb,
childIntraSegmentMessagesInKbPerProducer));
childFR->addNumRemoteToMSGTime(
MINOF(csOne,childUpRemoteSegmentMessagesPerProducer));
childFR->addKBRemoteToMSGTime(
MINOF(messageBufferSizeInKb,
childRemoteSegmentMessagesInKbPerProducer));
// childFR->setNormalMemory( childMemory );
childFR->setNumProbes( numOfProbes );
//---------------
// Child Last Row
//---------------
childLR = new STMTHEAP SimpleCostVector;
copyCost = childIntraCPUCopyLengthPerProducer * instrToCopyA1KBMessage +
childIntraSegmentCopyLengthPerProducer * intraSegmentInstrToCopyA1KBMessage +
childRemoteSegmentCopyLengthPerProducer * intraSegmentInstrToCopyA1KBMessage;
// this used to be remoteSegmentInstrToCopyA1KBMessage; calibration?
CostScalar adjustment=upRowsPerConsumer;
NADefaults &defs = ActiveSchemaDB()->getDefaults();
CostScalar adjFact = defs.getAsLong(COMP_INT_61);
// If this exchange is partitioning too few rows is it really needed?
// Adjust rows such that it is atleast numOfConsumers * numOfProducers
// do not do this if it is in Dp2 or if number of consumers is one
// this is primarily because partial group-by cardinality is
// under-estimated
/*if ((upRowsPerConsumer * numOfConsumers < numOfConsumers * numOfProducers ||
upRowsPerConsumer * numOfConsumers < adjFact ) &&
numOfConsumers > csOne &&
NOT childExecutesInDP2)
{
adjustment = numOfConsumers * numOfProducers;
if (adjustment < adjFact)
adjustment = adjFact;
}*/
childLR->setInstrToCPUTime( copyCost +
adjustment * cpuCostHashRow
);
childLR->addNumLocalToMSGTime( childUpIntraSegmentMessagesPerProducer );
childLR->addKBLocalToMSGTime( childIntraSegmentMessagesInKbPerProducer );
childLR->addNumRemoteToMSGTime(childUpRemoteSegmentMessagesPerProducer );
childLR->addKBRemoteToMSGTime( childRemoteSegmentMessagesInKbPerProducer );
// childLR->setNormalMemory( childMemory );
childLR->setNumProbes( numOfProbes );
if (adjustmentForParallelPlans)
{
childFR->scaleByValue(csZero);
childLR->scaleByValue(csZero);
}
//----------------------------------------------------------------
// Compute the cost of starting ESPs as an idle time cost. Store
// this idle time in both the first row and last row vectors.
//----------------------------------------------------------------
//SimpleCostVector* espStartupCost = computeESPCost(NOT executeInDP2,
// numOfProbes);
//CostScalar espElapsedTime = espStartupCost->getCPUTime();
// + espStartupCost->getIOTime();
// computeEXPCost has been simplified by removing IO component. Nov.2005
CostScalar espElapsedTime = computeESPCost(NOT childExecutesInDP2,numOfProbes);
// ESP startup cost is waited, so must pay the price of ESP startup
// for each consumer, according to Hans (4/20/99).
espElapsedTime *= numOfConsumers;
CostScalar startupAdj = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(COMP_INT_63);
if(isOpBelowRoot_ && (NOT childExecutesInDP2) && ( numOfProbes < 10 ) && (CmpCommon::getDefault(COMP_BOOL_122) == DF_ON) ){
parentFR->addToCpuTime( (espElapsedTime * startupAdj) / numOfProbes );
parentLR->addToCpuTime( (espElapsedTime * startupAdj) / numOfProbes );
}
} // CostMethodExchange::produceCostVectors()
//<pb>
//==============================================================================
// Compute cost object for Exchange operator given the parent and child first
// row and last row cost vectors.
//
// Input:
// parentFR -- resources used by parent to produce first row.
//
// parentFR -- resources used by parent to produce last row.
//
// childFR -- resources used by child to produce first row.
//
// childFR -- resources used by child to produce last row.
//
// numOfProducers -- number of child processes receiving down messages and
// sending up messages.
//
// numOfConsumers -- number parent processes receiving up messages and sending
// down messages.
//
// Output:
// none
//
// Return:
// Pointer to cost object for exchange operator.
//
//==============================================================================
Cost*
CostMethodExchange::computeExchangeCost( const CostVecPtr parentFR,
const CostVecPtr parentLR,
const CostVecPtr childFR,
const CostVecPtr childLR,
const CostScalar & numOfConsumers,
const CostScalar & numOfProducers ) const
{
//----------------------------------------------------------------------------
// When computing an Exchange's cost object, we must construct the parent and
// child cost objects separately because they could possibly use a different
// number of CPUs and have a different number of streams per CPU. By
// constructing separate costs objects for the parent and child we ensure that
// the first row and last row costs are normalized appropriately per CPU and
// per stream. Once both the parent and child costs have been normalized, we
// can safely combine them into a single cost object.
//----------------------------------------------------------------------------
//---------------------
// Compute Child cost.
//---------------------
Cost * childExchangeCost =
new STMTHEAP Cost( childFR,
childLR,
NULL,
Lng32(numOfProducers.getValue()),
1 );
//---------------------
// Compute Parent cost.
//---------------------
Cost * parentExchangeCost =
new STMTHEAP Cost( parentFR,
parentLR,
NULL,
Lng32(numOfConsumers.getValue()),
1 );
//-------------------------------------------------------------------
// When we get a CPU map we can possibly overlay the addition of the
// send and receive costs if they don't both occur on common CPUs.
//-------------------------------------------------------------------
//------------------------------------------------------------------
// Finally, combine normalized parent and child costs into a single
// Exchange cost object
//------------------------------------------------------------------
SimpleCostVector cvFR = childExchangeCost->getCpfr()
+ parentExchangeCost->getCpfr();
SimpleCostVector cvLR = childExchangeCost->getCplr()
+ parentExchangeCost->getCplr();
Cost* exchangeCost =
new STMTHEAP Cost( &cvFR,
&cvLR,
NULL, // No blocking vector for exchange.
1, // vectors already normalized to number of CPUs
1 ); // and plan fragments per CPU
//----------------------------------------------------------------------
// Now set number of streams per CPU and count of CPUs correctly for
// Exchange. It won't ever be used (today) - we are just doing this for
// consistency.
//----------------------------------------------------------------------
exchangeCost->planFragmentsPerCPU() = 1;
exchangeCost->countOfCPUs() = Lng32(numOfConsumers.getValue());
//-----------------------------------------------
// As good citizens we clean up after ourselves.
//-----------------------------------------------
delete childExchangeCost;
delete parentExchangeCost;
return exchangeCost;
} // CostMethodExchange::computeExchangeCost()
//<pb>
/**********************************************************************/
/* */
/* CostMethodFileScan */
/* */
/**********************************************************************/
Cost*
CostMethodFileScan::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
FileScan* p = (FileScan*)op; // downcast
Cost *costPtr = NULL;
// short cut for hbase for now
// if (p->isHbaseTable()) {
//
// CostScalar outputRowSize = 100 /* getEstimatedRecordLength*/;
// CostScalar outputRowSizeFactor = scmRowSizeFactor(outputRowSize);
//
// Cost* hbaseSCanCost= scmCost(100,
// 100,
// csZero,
// csZero,
// csZero,
// noOfProbesPerStream_,
// csZero,
// csZero,
// outputRowSize,
// csZero);
// return hbaseSCanCost;
// }
// The fileScanOptimizer performs several actions:
// 1. Decides the access method for the scan
// 2.- Computes the cost for the access method
// 3.- Builds a key for the access method and attaches
// that key to the scan.
// 4.- The FileScanOptimizer is side-effect free (i.e. it
// won't side-effect its paraemters)
// This cannot be computed in the filescan optimizer since
// outputLogProp is not const (and due to lazy evaluation of
// log. properties) cannot be made into a const method:
CostScalar resultSetCardinality =
p->getGroupAttr()->
outputLogProp(myContext->getInputLogProp())->getResultCardinality();
// $$$ Due to a bug in histograms (up to tag A091197_1)
// $$$ sometimes the cardinality is negative, if so, fix it
// $$$ to pass regressions:
if ( resultSetCardinality.isLessThanZero() /* < csZero */ )
resultSetCardinality = CostScalar(100.0);
ValueIdSet
externalInputs(p->getGroupAttr()->getCharacteristicInputs());
// ---------------------------------------------------------------------
// Apply partitioning key predicates if necessary
// ---------------------------------------------------------------------
// Scan is a leaf so it has its physical properties available
LogPhysPartitioningFunction *logPhysPartFunc =
(LogPhysPartitioningFunction *) // cast away const
myContext->getPlan()->getPhysicalProperty()->getPartitioningFunction()->
castToLogPhysPartitioningFunction();
if ( logPhysPartFunc != NULL )
{
LogPhysPartitioningFunction::logPartType logPartType =
logPhysPartFunc->getLogPartType();
if ( logPartType == LogPhysPartitioningFunction::LOGICAL_SUBPARTITIONING
OR logPartType == LogPhysPartitioningFunction::HORIZONTAL_PARTITION_SLICING
)
{
// We need to add the partition input values so that
// they are considered when putting together the keys
externalInputs +=
logPhysPartFunc->getLogPartitioningFunction()->getPartitionInputValues();
}
}
ScanOptimizer *scanOptimizer =
ScanOptimizer::getScanOptimizer(*p /*in, not side-effected */
,resultSetCardinality /* in */
,*myContext
,externalInputs
,STMTHEAP
);
// -----------------------------------------------------------------------
// $$$ Below we would like to use the virtual function
// mechanism to get back a single object of type ScanKey&,
// however, in order to do this I would have to add a
// preCodeGen implementation to class SearchKey. This takes
// some work and I'd rather do other things for now. But
// in the future the SearchKey and Mdamkey generic
// behaviour should be completely unified by their ancestor
// ScanKey.
// -----------------------------------------------------------------------
SearchKey *searchKeyPtr = NULL;
MdamKey *mdamKeyPtr = NULL;
NABoolean replicateKeyPredsBecauseOfKeyKludge = FALSE;
NABoolean
isNestedJoin = ( myContext->getInputLogProp()->getColStats().entries() > 0 );
// excluded for coverage because DEBUG only code
if (CURRSTMT_OPTDEFAULTS->optimizerHeuristic2()) {//#ifndef NDEBUG
if (isNestedJoin)
(*CURRSTMT_OPTGLOBALS->nestedJoinMonitor).enter();
(*CURRSTMT_OPTGLOBALS->fileScanMonitor).enter();
}//#endif
costPtr = scanOptimizer->optimize(searchKeyPtr, /* out */
mdamKeyPtr /* out */);
// excluded for coverage because DEBUG only code
if (CURRSTMT_OPTDEFAULTS->optimizerHeuristic2()) {//#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->fileScanMonitor).exit();
if (isNestedJoin)
(*CURRSTMT_OPTGLOBALS->nestedJoinMonitor).exit();
}//#endif
// Set blocks per access estimate. Use value from the defaults
// table if set by the user. If not set, compute it.
Lng32 blocksPerAccess = (Lng32)CURRSTMT_OPTDEFAULTS->getNumOfBlocksPerAccess();
if (blocksPerAccess == 0)
{
// Defaults table must have specified SYSTEM.
blocksPerAccess = scanOptimizer->getNumberOfBlocksToReadPerAccess();
}
// Check if user overrode the blocks per access estimate via CQ Shape.
// If so, override the value from the defaults table or our computation.
const ReqdPhysicalProperty* propertyPtr =
myContext->getReqdPhysicalProperty();
if ( propertyPtr
&& propertyPtr->getMustMatch()
&& (propertyPtr->getMustMatch()->getOperatorType()
== REL_FORCE_ANY_SCAN))
{
ScanForceWildCard* scanForcePtr =
(ScanForceWildCard*)propertyPtr->getMustMatch();
if (scanForcePtr->getNumberOfBlocksToReadPerAccess() > -1)
blocksPerAccess = scanForcePtr->getNumberOfBlocksToReadPerAccess();
}
// Get estimated Dp2 rows accessed from ScanOptimizer.
CostScalar estRowsAccessed = scanOptimizer->getEstRowsAccessed();
// Set blocks read (hint to DP2)
p->setNumberOfBlocksToReadPerAccess(blocksPerAccess);
p->setEstRowsAccessed(estRowsAccessed);
if (searchKeyPtr)
{
// Single subset was chosen, set the key:
p->setSearchKey(searchKeyPtr);
}
else
{
// Mdam was chosen, set the key:
p->setMdamKey(mdamKeyPtr);
}
// For the scan, the streams are given by the number of
// active partitions (when cost is going down this number
// may be wrong because of logical partitioning may actually
// increase (or decrease) the number of *real* partitions.
// When cost is going up we get the streams from the
// *real* part. func. in the phys. props
// The file scan optimizer figures this out in the method below
if (CmpCommon::getDefault(NCM_HBASE_COSTING) == DF_ON)
countOfStreams =
(Lng32)scanOptimizer->getEstNumActivePartitionsAtRuntimeForHbaseRegions();
else
countOfStreams = (Lng32)scanOptimizer->getNumActivePartitions();
// ------------------------------------------------------------------------
// If we are on the right leg of a parallel nested join, the
// number of probes is 1, and we are underneath a materialize
// that does not pass the probes through, then we are going to
// have to scan the entire table N times, where N is the required
// number of logical partitions.
// ------------------------------------------------------------------------
const ReqdPhysicalProperty* rpp = myContext->getReqdPhysicalProperty();
const LogicalPartitioningRequirement *lpr =
rpp->getLogicalPartRequirement();
PartitioningRequirement * logPartReq = NULL;
if (lpr != NULL)
logPartReq = lpr->getLogReq();
const CostScalar & noOfProbes =
( myContext->getInputLogProp()->getResultCardinality() ).minCsOne();
ValueIdSet outerRefs;
externalInputs.getOuterReferences(outerRefs);
if (logPartReq
AND logPartReq ->isRequirementReplicateNoBroadcast() // parallel n.j.
AND (noOfProbes == 1) // 1 probe, but
AND outerRefs.isEmpty()) //no probe values
{
// We have replicate no broadcast underneath a materialize that
// does not pass the probes through. This means we are on the
// right leg of a parallel nested join and each ESP is going to
// have to read the entire table, but the scan costing was
// unaware of this. Multiply the last row and total cost
// by the degree of parallelism.
const CostScalar & numParts = logPartReq->getCountOfPartitions();
costPtr->cplr() = costPtr->getCplr() * numParts;
costPtr->totalCost() = costPtr->getTotalCost() * numParts;
}
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
// excluded for coverage because DEBUG only code
#ifndef NDEBUG
if ( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON )
{
pfp = stdout;
fprintf(pfp,"FILESCAN::computeOperatorCost()\n");
costPtr->print(pfp);
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
// transfer probe counters to p (the FileScan)
p->setProbes(scanOptimizer->getProbes());
p->setSuccessfulProbes(scanOptimizer->getSuccessfulProbes());
p->setUniqueProbes(scanOptimizer->getUniqueProbes());
p->setDuplicatedSuccProbes(scanOptimizer->getDuplicateSuccProbes());
p->setTuplesProcessed(scanOptimizer->getTuplesProcessed());
delete scanOptimizer;
return costPtr;
} // CostMethodFileScan::computeOperatorCostInternal()
//<pb>
/**********************************************************************/
/* */
/* CostMethodDP2Scan */
/* */
/**********************************************************************/
Cost*
CostMethodDP2Scan::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
DP2Scan* p = (DP2Scan*)op; // downcast
// The DP2 costing is exactly as the file scan, thus use
// the filescan's costing here:
Cost *costPtr = CostMethodFileScan::computeOperatorCostInternal( op,
myContext, countOfStreams );
return costPtr;
} // CostMethodDP2Scan::computeOperatorCostInternal()
//<pb>
// ----QUICKSEARCH FOR FIXEDROW...........................................
/**********************************************************************/
/* */
/* CostMethodFixedCostPerRow */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodFixedCostPerRow::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodFixedCostPerRow::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// This CostMethod basically computes a CPU cost for operators whose
// costs are not that important. It applies the formula:
// baseCpuCost_ +
// cpuCostPerChildRow_ * child0RowCount_ +
// cpuCostPerOutputRow_ * myRowCount_, where the row counts are those
// of the total result set, and then amortize the cost across streams.
// It takes the first row count to be just its last row count amortized
// across the no of probes.
// ---------------------------------------------------------------------
CostScalar cpu = baseCpuCost_;
if(op->getArity() > 0)
{
EstLogPropSharedPtr child0LogProp = op->child(0).outputLogProp(inLogProp_);
cpu += cpuCostPerChildRow_ * child0LogProp->getResultCardinality();
}
cpu += cpuCostPerOutputRow_ * myRowCount_;
// ---------------------------------------------------------------------
// Synthesize the cost vectors.
// ---------------------------------------------------------------------
SimpleCostVector cvLR (
cpu/countOfStreams_ * CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions(),
csZero,csZero,csZero,
noOfProbesPerStream_);
SimpleCostVector cvFR (
cpu/countOfStreams_/noOfProbesPerStream_ // converting CPU instr
* CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions(), //into time
csZero,csZero,csZero,
noOfProbesPerStream_);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"FIXEDCOST::computeOperatorCost()\n");
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return the cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP Cost( &cvFR,
&cvLR,
NULL,
cpuCount,
fragmentsPerCPU
);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodFixedCostPerRow::computeOperatorCostInternal().
//<pb>
// -----------------------------------------------------------------------
// CostMethodFixedCostPerRow::print().
// -----------------------------------------------------------------------
void CostMethodFixedCostPerRow::print(FILE* ofd,
const char* indent,
const char* title) const
{
BUMP_INDENT(indent);
CostMethod::print(ofd,indent,title);
fprintf(ofd,"%s ",NEW_INDENT);
fprintf(ofd,
"baseCpuCost=%g cpuCostPerChildRow=%g cpuCostPerOutputRow=%g",
baseCpuCost_.value(),
cpuCostPerChildRow_.value(),
cpuCostPerOutputRow_.value());
fprintf(ofd,"\n ");
} // CostMethodFixedCostPerRow::print()
//<pb>
// ----QUICKSEARCH FOR SORT...............................................
/**********************************************************************/
/* */
/* CostMethodSort */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodSort cacheParameters().
// -----------------------------------------------------------------------
void CostMethodSort::cacheParameters(RelExpr* op,
const Context* myContext)
{
CostMethod::cacheParameters(op,myContext);
// Get addressability to the defaults table and extract default memory.
// CacheParameters() can set memoryLimit_ to > 20MB in some cases, for
// example if the query tree contains REL_GROUPBY operator. This will
// result in the assumption that even BMO plans can be sorted in memory,
// , but executor uses internal sorts only if the sort table size < 20MB.
NADefaults &defs = ActiveSchemaDB()->getDefaults();
memoryLimit_ = defs.getAsDouble(MEMORY_UNITS_SIZE);
sort_ = (Sort*) op_;
ValueIdSet sortKeyVis;
if(sort_->getSortKey().isEmpty())
{
CMPASSERT(NOT sort_->getArrangedCols().isEmpty());
sortKeyVis = sort_->getArrangedCols();
}
else
{
sortKeyVis.insertList(sort_->getSortKey());
}
Lng32 myRowLength = myVis().getRowLength();
// allocate space for atleast 2 rows
memoryLimit_ = MAXOF(memoryLimit_, 2 * myRowLength);
sortKeyLength_ = sortKeyVis.getRowLength();
sortRecLength_ = sortKeyLength_ + myRowLength;
// ---------------------------------------------------------------------
// The key of a row is encoded. A copy of the row (together with the
// encoded key) is then made to a buffer the Sort session allocates.
// ---------------------------------------------------------------------
cpuCostSendRow_ = CostPrimitives::cpuCostForEncode(sortKeyVis) +
CostPrimitives::cpuCostForCopyRow(myRowLength);
// ---------------------------------------------------------------------
// Cost to compare the keys of two rows.
// ---------------------------------------------------------------------
cpuCostCompareKeys_ = CostPrimitives::cpuCostForCompare(sortKeyVis);
// ---------------------------------------------------------------------
// Executor allocates result buffer to hold the row it receives. This
// is the cost to make a copy of the row to that buffer.
// ---------------------------------------------------------------------
cpuCostCopyResultRow_ = CostPrimitives::cpuCostForCopyRow(myRowLength);
// ---------------------------------------------------------------------
// Determine the max run generation order and merge order if there is
// a memory limit.
// ---------------------------------------------------------------------
if(isBMO_)
{
// Save two buffers' memory for the double output buffering.
double memory = memoryLimit_ - ioBufferSize_ * 2.;
// Memory allocated to a BMO should not be that small.
CMPASSERT(memory > 0.)
// -------------------------------------------------------------------
// Memory needs for each row during run generation. We need storage
// for the row itself and its associated tree node.
// -------------------------------------------------------------------
double memoryForEachRowAtRunGenPhase =
(sortRecLength_ / 1024. + treeNodeSize_);
maxRunGenOrder_ = (memory / memoryForEachRowAtRunGenPhase);
// Memory allocated to a BMO should not be that small.
CMPASSERT(maxRunGenOrder_ >= 2.)
// -------------------------------------------------------------------
// Memory needs for each run during a merge phase. We need a buffer
// and an internal node for each run.
// -------------------------------------------------------------------
double memoryForEachRunAtMergePhase = (ioBufferSize_ + treeNodeSize_);
maxMergeOrder_ = (memory / memoryForEachRunAtMergePhase);
// Memory allocated to a BMO should not be that small.
CMPASSERT(maxMergeOrder_ >= 2.);
}
else
// ---------------------------------------------------------------------
// No memory limit. Just do internal sort.
// ---------------------------------------------------------------------
{
maxRunGenOrder_ = maxMergeOrder_ = 0.;
}
} // CostMethodSort::cacheParameters().
//<pb>
// -----------------------------------------------------------------------
// CostMethodSort computeOperatorCost().
// -----------------------------------------------------------------------
Cost*
CostMethodSort::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// Cost scalars to be computed.
// ---------------------------------------------------------------------
CostScalar cpuBK(csZero), ioSeekBK(csZero), ioByteBK(csZero); //j memBK(csZero), diskBK(csZero);
CostScalar cpuLR(csZero), ioSeekLR(csZero), ioByteLR(csZero); //j memLR(csZero), diskLR(csZero);
CostScalar rowCount(csZero);
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
CostScalar myRowCountPerStream =
myLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
rowCount = ( myRowCountPerStream / noOfProbesPerStream_ ).minCsOne();
}
else
{
// Row count a single probe on one instance of this sort is processing.
rowCount =
( myRowCount_ / countOfStreams_ / noOfProbesPerStream_ ).minCsOne();
}
// Memory requirements of the whole table to be sorted.
// Add 12 bytes for executor's tuple descriptor size.
CostScalar tableSize = (rowCount * (sortRecLength_ + 12)) / csOneKiloBytes;
// ---------------------------------------------------------------------
// Per probe setup costs. This captures the cost to call the method
// SortUtil::SortInitialize() to allocate the SortAlgo objects (such as
// Tree, TreeNode, Record and ScratchSpace), etc.
// ---------------------------------------------------------------------
cpuBK += cpuCostPerProbeInit_;
// ---------------------------------------------------------------------
// The key of a row is encoded. A copy of the row (together with the
// encoded key) is then made to a buffer the Sort session allocates.
// ---------------------------------------------------------------------
cpuBK += cpuCostSendRow_ * rowCount;
// ---------------------------------------------------------------------
// If this is not a BMO, we can use as much memory as we wish. Assume
// an internal quick sort session will be done, which takes O(n*logn)
// comparisons. Assume the same case if the whole file can fit into
// memory.
// ---------------------------------------------------------------------
if( ((NOT isBMO_) OR (tableSize < memoryLimit_)) )
{
//j memBK += tableSize;
cpuBK += cpuCostCompareKeys_ * qsFludgeFactor_ *
rowCount * log(rowCount.value());
}
else
{
CMPASSERT(rowCount >= maxRunGenOrder_);
// -------------------------------------------------------------------
// We have to do run generation. Each row is put into a tournament
// tree and the new winner of the tree is determined in O(h) time,
// where h is the height of the tree.
// -------------------------------------------------------------------
CostScalar h = log(maxRunGenOrder_.value()) / log(2.);
cpuBK += cpuCostCompareKeys_ * rsFludgeFactor_ * h * rowCount;
// -------------------------------------------------------------------
// The whole table is flushed to disk.
// -------------------------------------------------------------------
ioSeekBK += tableSize / ioBufferSize_;
ioByteBK += tableSize;
//j diskBK += tableSize;
// -------------------------------------------------------------------
// The expected no of rows in each run generated from Replacement
// Selection is twice the number of nodes in the tree.
// -------------------------------------------------------------------
CostScalar rowCountPerRun = MINOF( maxRunGenOrder_ * csTwo, rowCount );
CostScalar noOfRuns = (rowCount / rowCountPerRun).getCeiling();
// -------------------------------------------------------------------
// Intermediate merge passes are needed if we have generated more
// runs than the max merge order.
// -------------------------------------------------------------------
if(maxMergeOrder_ < noOfRuns)
{
// -----------------------------------------------------------------
// Should intermediate pass ever occur, we need twice the disk
// space since both the input and output files of the intermediate
// pass have to reside on the disk at the same time.
// -----------------------------------------------------------------
//j diskBK += tableSize;
// -----------------------------------------------------------------
// During each intermediate pass, (noOfRuns/maxMergeOrder_) merge
// sessions are executed. Each merge session merges maxMergeOrder_
// runs into a single run. If the number of runs left after a pass
// remains to be larger than maxMergeOrder_, a second pass has to
// be done. Eventually, [log(runCount) / log(maxMergeOrder) - 1]
// intermediate passes are resulted.
// -----------------------------------------------------------------
CostScalar noOfIntPasses =
log(noOfRuns.value()) / log(maxMergeOrder_.value()) - 1.;
// -----------------------------------------------------------------
// Sum of CPU costs for all merge sessions.
// -----------------------------------------------------------------
CostScalar h = log(maxMergeOrder_.value()) / log(2.);
CostScalar cpuCostPerIntPass =
cpuCostCompareKeys_ * rsFludgeFactor_ * h * rowCount;
cpuBK += cpuCostPerIntPass * noOfIntPasses;
// -----------------------------------------------------------------
// Summing up the effect of all its sessions, each pass involves
// reading the whole table and writing the whole table once.
// -----------------------------------------------------------------
ioSeekBK += tableSize / ioBufferSize_ * csTwo * noOfIntPasses;
ioByteBK += tableSize * csTwo * noOfIntPasses;
// We have maxMergeOrder_ runs left after these intermediate passes.
noOfRuns = maxMergeOrder_;
}
// -------------------------------------------------------------------
// Since the tree node and buffers are pre-allocated to use the max
// run generation and merge orders, we use up all memory available.
// -------------------------------------------------------------------
//j memBK = memoryLimit_;
// -------------------------------------------------------------------
// We could start producing rows in the final merge phase after at
// least a buffer from each run is read into memory.
// -------------------------------------------------------------------
ioSeekBK += noOfRuns;
ioByteBK += MINOF(noOfRuns * ioBufferSize_,tableSize);
// -------------------------------------------------------------------
// From this point onwards, sort can start producing rows, while it's
// doing a final merge of its runs. We decide to continue computing
// a per-probe LR cost and scale it up by the no of probes at the end.
// -------------------------------------------------------------------
// -------------------------------------------------------------------
// CPU cost for the final merge phase. We will allocate a tree only
// for the runs we have, which can be fewer than maxMergeOrder_.
// -------------------------------------------------------------------
CMPASSERT(noOfRuns <= maxMergeOrder_);
h = MIN_ONE(log(noOfRuns.value()) / log(2.));
cpuLR += cpuCostCompareKeys_ * rsFludgeFactor_ * h * rowCount;
// -------------------------------------------------------------------
// IO cost. The whole table is read exactly once. NB. We needn't read
// the first buffer for each run since the cost has been charged to
// BK cost already.
// -------------------------------------------------------------------
ioSeekLR += MAXOF( tableSize / ioBufferSize_ - noOfRuns, csZero );
ioByteLR += MAXOF( tableSize - noOfRuns * ioBufferSize_, csZero );
// -------------------------------------------------------------------
// Memory used in the final merge phase.
// -------------------------------------------------------------------
//j memLR =
//j noOfRuns * (ioBufferSize_ + treeNodeSize_) + ( csTwo * ioBufferSize_ );
}
// ---------------------------------------------------------------------
// Sort receive part of the executor. Executor allocates result buffer
// to hold the row it receives.
// ---------------------------------------------------------------------
cpuLR += (cpuCostAllocateBuffer_ * tableSize / exBufferSize_);
cpuLR += (cpuCostAllocateTuple_ + cpuCostCopyResultRow_) * rowCount;
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
const CostScalar ff_seeks = CURRSTMT_OPTDEFAULTS->getTimePerSeek();
// BMO (external sort) involves both read and write, getTimePerSeqKb returns
// time taken to read 1KB of data sequentially, since write takes more time
// than read, we need some adjustment (read-write fudge factor) here.
const CostScalar ff_seqIO =
CostScalar(rwFudgeFactor_ * CURRSTMT_OPTDEFAULTS->getTimePerSeqKb());
const CostScalar ff_local_msgs = CURRSTMT_OPTDEFAULTS->getTimePerLocalMsg();
const CostScalar ff_kblocal_msgs = CURRSTMT_OPTDEFAULTS->getTimePerKbOfLocalMsg();
SimpleCostVector cvBK (
cpuBK * ff_cpu,
ioSeekBK * ff_seeks + ioByteBK * ff_seqIO,
ioSeekBK * ff_local_msgs + ioByteBK * ff_kblocal_msgs,
csZero,
noOfProbesPerStream_
);
SimpleCostVector cvFR (
csZero,csZero,csZero,csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// Scale last row cost scalars up by the no of probes. Disk and memory
// are not scaled up since they are reusable across probes.
// ---------------------------------------------------------------------
cpuLR *= noOfProbesPerStream_;
ioSeekLR *= noOfProbesPerStream_;
ioByteLR *= noOfProbesPerStream_;
//j diskLR = diskBK;
SimpleCostVector cvLR (
cpuLR * ff_cpu,
ioSeekLR * ff_seeks + ioByteLR * ff_seqIO,
ioSeekLR * ff_local_msgs + ioByteLR * ff_kblocal_msgs,
csZero,
noOfProbesPerStream_
);
//----------------------------------------------------------------------
// check if this is a partial cost; we reduce the cost by a factor
// influenced by maximum window size of the partial sort
//----------------------------------------------------------------------
Sort *sOp = (Sort *)op;
if (! sOp->getPrefixSortKey().isEmpty())
{
ColStatDescList& myPartSortColStats = myLogProp_->colStats();
ValueIdSet partSort = sOp->getPrefixSortKey();
CollIndex length = sOp->getPrefixSortKey().entries();
CostScalar maxWindowSize(myRowCount_)
,temp;
for (CollIndex i = 0; i < length; i++)
{
temp =
myPartSortColStats.getMaxOfMaxFreqOfCol(
sOp->getPrefixSortKey().at(i).getItemExpr()->
removeInverseOrder()->getValueId()
);
if ((temp.isGreaterThanOne()) &&
(maxWindowSize > temp))
maxWindowSize = temp;
}
CostScalar sortAdjstFactor(csZero);
CostScalar sortFactor = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(PARTIAL_SORT_ADJST_FCTR);
if (maxWindowSize.isGreaterThanOne())
sortAdjstFactor = (maxWindowSize/myRowCount_) * sortFactor;
if ( sortAdjstFactor == csZero)
sortAdjstFactor = sortFactor;
cvLR.scaleByValue(sortAdjstFactor);
cvBK.scaleByValue(sortAdjstFactor);
cvFR.scaleByValue(sortAdjstFactor);
}
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"SORT::computeOperatorCost()\n");
fprintf(pfp,"myRowCount=%g\n",myRowCount_.value());
cvBK.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP Cost( &cvFR,
&cvLR,
&cvBK,
cpuCount,
fragmentsPerCPU
);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodSort::computeOperatorCostInternal()
//<pb>
// ----QUICKSEARCH FOR GROUPBY............................................
/**********************************************************************/
/* */
/* CostMethodGroupByAgg */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodGroupByAgg::cacheParameters().
// -----------------------------------------------------------------------
void CostMethodGroupByAgg::cacheParameters(RelExpr* op,
const Context* myContext)
{
// ---------------------------------------------------------------------
// Comments on GroupBy estimated logical properties: Estimated Logical
// Properties synthesis for GroupBy operators do not distinguish among
// partial or full aggregation. As a result, if we split GroupBy's into
// several levels of execution (right now, we can have at most three),
// Estimated Logical Properties will always report that the GroupBy at
// the lowest level does all the work (ie. a full grouping) so that all
// the remaining GroupBy's above report the same input and output row
// counts. This problem needs to be addressed, but it looks like some
// work on the infrastructure needs to be done. Right now, the tentative
// plan is to have physical costing do something to "estimate" actual
// row counts in these cases. (See code in estimateParallelism()), but
// too little can be done about it at this level.
//
// Added on 1/27/98: This is no longer true. Some recent code written
// estimates the output row count of a partial group by
// leaf by treating as if the node executes in ONE single DP2. Although
// this sounds like an improvement, if the no of instances of DP2 where
// this partial group by executes on is not ONE, the number of partial
// groups still need to be re-estimated.
// ---------------------------------------------------------------------
CostMethod::cacheParameters(op,myContext);
gb_ = (GroupByAgg *) op;
// We will now get the Estimated Logical Properties of the child of
// Materialize; also multipleCalls will tell us whether or not such
// child gets called only once.
Int32 multipleCalls;
EstLogPropSharedPtr modInputLP;
if (inLogProp_->getResultCardinality().isGreaterThanOne() &&
CmpCommon::getDefault(COSTING_SHORTCUT_GROUPBY_FIX) != DF_ON)
{
// Executor doesn't materialize grouby operator, but optimizer assumes
// it does. Because of this we under estimate groupby cost when it
// is right side of NJ. This CQD is being used since it is already
// available and fix is generic, not specific to short cut grby.
modInputLP = gb_->child(0).getGroupAttr()
->materializeInputLogProp(inLogProp_, &multipleCalls);
}
else
modInputLP = inLogProp_;
// if inputForSemiTSJ is set for inputEstLogProp it cannot be passed
// below the filter so create a new EstLogProp with the flag set off to
// pass to my children
//
EstLogPropSharedPtr copyInputEstProp;
if (modInputLP->getInputForSemiTSJ() != EstLogProp::NOT_SEMI_TSJ)
{
copyInputEstProp = EstLogPropSharedPtr(new (HISTHEAP)
EstLogProp(*modInputLP));
copyInputEstProp->setInputForSemiTSJ(EstLogProp::NOT_SEMI_TSJ);
}
else
{
copyInputEstProp = modInputLP;
}
child0LogProp_ = gb_->child(0).outputLogProp(copyInputEstProp);
myIntLogProp_ = gb_->getGroupAttr()->intermedOutputLogProp(copyInputEstProp);
CMPASSERT(child0LogProp_ != NULL);
child0RowCount_ = ( child0LogProp_->getResultCardinality() ).minCsOne();
CMPASSERT(myIntLogProp_ != NULL);
groupCount_ = ( myIntLogProp_->getResultCardinality() ).minCsOne();
groupCount_ = MINOF( child0RowCount_, groupCount_ );
const ValueIdSet& grbyVis = gb_->groupExpr();
const ValueIdSet& aggrVis = gb_->aggregateExpr();
const ValueIdSet& predVis = gb_->selectionPred();
// Length in bytes of the group key and the aggregates.
groupKeyLength_ = (grbyVis.isEmpty() ? 0 : grbyVis.getRowLength());
aggregateLength_ = (aggrVis.isEmpty() ? 0 : aggrVis.getRowLength());
// ---------------------------------------------------------------------
// Per probe init cost. Subclasses should refine it.
// ---------------------------------------------------------------------
cpuCostPerProbeInit_ = csZero;
// ---------------------------------------------------------------------
// Cost to initialize a new group.
// ---------------------------------------------------------------------
cpuCostInitNewGroup_ =
CostPrimitives::getBasicCostFactor(EX_OP_ALLOCATE_TUPLE) +
CostPrimitives::cpuCostForCopySet(grbyVis) +
CostPrimitives::cpuCostForCopyRow(aggregateLength_);
// ---------------------------------------------------------------------
// CPU cost for comparing the group keys.
// ---------------------------------------------------------------------
cpuCostCompareGroupKeys_ = CostPrimitives::cpuCostForCompare(grbyVis);
// ---------------------------------------------------------------------
// CPU cost for aggregating a row with an existing group.
// ---------------------------------------------------------------------
cpuCostAggrRowToGroup_ = CostPrimitives::cpuCostForAggrRow(aggrVis);
// ---------------------------------------------------------------------
// CPU cost for evaluating the having predicate on a group.
// ---------------------------------------------------------------------
cpuCostEvalHavingPred_ = CostPrimitives::cpuCostForEvalPred(predVis);
// ---------------------------------------------------------------------
// CPU cost to return a qualified row.
// ---------------------------------------------------------------------
cpuCostReturnRow_ = CostPrimitives::getBasicCostFactor(EX_OP_COPY_ATP);
} // CostMethodGroupByAgg::cacheParameters().
//<pb>
// -----------------------------------------------------------------------
// CostMethodGroupByAgg::estimateDegreeOfParallelism().
//
// This method computes five parameters:
// CostMethod::countOfStreams_,
// CostMethod::noOfProbesPerStream_,
// CostMethodGroupByAgg::rowCountPerStream_,
// CostMethodGroupByAgg::groupCountPerStream_,
// CostMethodGroupByAgg::myRowCountPerStream_.
//
// Note that it doesn't make use of the base class's implementation of
// estimateDegreeOfParallelism(). It refines that implementation.
// -----------------------------------------------------------------------
void CostMethodGroupByAgg::estimateDegreeOfParallelism()
{
const ValueIdSet& grbyVis = gb_->groupExpr();
// rpp_ should not be NULL for a GroupBy operator.
CMPASSERT(rpp_);
const PartitioningFunction* pf = partFunc_;
const PartitioningRequirement* pr = partReq_;
EstLogPropSharedPtr groupEstLogProp;
if (myIntLogProp_->getResultCardinality() < child0RowCount_)
{
groupEstLogProp = myIntLogProp_;
}
else
{
groupEstLogProp = child0LogProp_;
}
// We are asked to compute a scalar aggregate with no grouping columns.
if(grbyVis.isEmpty())
{
// -------------------------------------------------------------------
// The topmost scalar aggregate has to act as the final consolidator
// which must therefore consume and produce only one stream, unless
// it is doing a replicateNoBroadcast.
// -------------------------------------------------------------------
if(gb_->isNotAPartialGroupBy() OR gb_->isAPartialGroupByRoot())
{
// Compute the number of streams.
if (pf != NULL)
// ---------------------------------------------------------------
// pf can exist when we're going up the tree. Grab the count of
// partitions from there. This is an accurate count.
// ---------------------------------------------------------------
countOfStreams_ = pf->getCountOfPartitions();
else if ((pr != NULL) AND
(pr->isRequirementReplicateNoBroadcast()))
countOfStreams_ = pr->getCountOfPartitions();
else
countOfStreams_ = 1;
// If this operator is on the right leg of a parallel nested join,
// then limit the countOfStreams_ by the number of probes, because
// if we have fewer probes than the number of streams, then some
// streams will be inactive.
if ((partReq_ != NULL) AND
partReq_->isRequirementReplicateNoBroadcast())
{
CostScalar tempCountOfStreams= MINOF(CostScalar(countOfStreams_),
noOfProbes_.getCeiling());
countOfStreams_ = Lng32(tempCountOfStreams.value());
}
// Compute the number of rows per stream.
// Added on 1/27/98: The partial grouping effect has finally
// been reflected in the child row count, so no need to
// special case a partial group by root.
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
// For input logical properties, we shall use all columns of the partitioning
// key. Passing NULL will ensure that
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
}
else
{
rowCountPerStream_ = ( child0RowCount_ / countOfStreams_ ).minCsOne();
noOfProbesPerStream_ = ( noOfProbes_/countOfStreams_ ).minCsOne();
}
groupCountPerStream_ = noOfProbesPerStream_;
myRowCountPerStream_ = noOfProbesPerStream_;
// Just in case. We want to keep (rowCount >= groupCount).
rowCountPerStream_ = MAXOF(rowCountPerStream_,groupCountPerStream_);
}
else if(gb_->isAPartialGroupByNonLeaf())
{
CMPASSERT(NOT rpp_->executeInDP2());
if (pf != NULL)
// ---------------------------------------------------------------
// pf can exist when we're going up the tree. Grab the count of
// partition from there. This is an accurate count.
// ---------------------------------------------------------------
countOfStreams_ = pf->getCountOfPartitions();
else if ((pr != NULL) AND
(pr->getCountOfPartitions() != ANY_NUMBER_OF_PARTITIONS))
countOfStreams_ = pr->getCountOfPartitions();
else
countOfStreams_ = rpp_->getCountOfPipelines();
// If this operator is on the right leg of a parallel nested join,
// then limit the countOfStreams_ by the number of probes, because
// if we have fewer probes than the number of streams, then some
// streams will be inactive.
if ((partReq_ != NULL) AND
partReq_->isRequirementReplicateNoBroadcast())
{
CostScalar tempCountOfStreams= MINOF(CostScalar(countOfStreams_),
noOfProbes_.getCeiling());
countOfStreams_ = Lng32(tempCountOfStreams.value());
}
// -----------------------------------------------------------------
// Each probe generates a request for each instance of PartialGroup
// ByNonLeaf. This number should be at least one!!!
// -----------------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = ( noOfProbes_ / countOfStreams_ ).minCsOne();
rowCountPerStream_ = ( child0RowCount_ / countOfStreams_ ).minCsOne();
}
// -----------------------------------------------------------------
// At for group and row count estimates, each stream of a Partial
// GroupByNonLeaf produces one group on one probe. Also, there
// must be a PartialGroupByLeaf below it in DP2 to help with the
// grouping. Each instance of these PartialGroupByLeaf is going to
// produce one row on each probe. Thus there should be as many rows
// to group as the no of partitions we have for the base table, and
// we don't know how many partitions there are at this point.
// -----------------------------------------------------------------
groupCountPerStream_ = noOfProbesPerStream_;
// -----------------------------------------------------------------
// This is a definitely unknown. As everywhere else, assume the base
// table is just partitioned into the same no of streams as we have
// in the ESP, so that each instance of this PartialGBNonLeaf takes
// one row only. It actually makes this PartialGBNonLeaf redundant,
// which is actually what we might want to do since two levels of
// groupby's seem to have introduced enough parallelism to do scalar
// aggregation.
//
// Added on 1/27/98: The partial grouping effect has finally
// been reflected in the child row count.
// -----------------------------------------------------------------
// rowCountPerStream_ = noOfProbesPerStream_;
rowCountPerStream_ = MAXOF(rowCountPerStream_, groupCountPerStream_);
// -----------------------------------------------------------------
// Shouldn't have any having predicates since this is not the final
// consolidator. In some rare cases it is possible to a having pred here
// If there are two nested joins above this partial groupby and there is
// a pred between columns from the outer tables of both NJs then that
// pred can end being pushed into this partial_gb_leaf. E.g. core/test002
// Genesis_10_000222_6892_r3
// -----------------------------------------------------------------
myRowCountPerStream_ = groupCountPerStream_;
}
else // a partial group by leaf.
{
if (pf != NULL)
// ---------------------------------------------------------------
// pf can exist when we're going up the tree. Grab the count of
// partition from there. This is an accurate count.
// ---------------------------------------------------------------
countOfStreams_ = pf->getCountOfPartitions();
else if ((pr != NULL) AND
(pr->getCountOfPartitions() != ANY_NUMBER_OF_PARTITIONS))
countOfStreams_ = pr->getCountOfPartitions();
else
// must underestimate, since we are on our way down.
countOfStreams_ = rpp_->getCountOfPipelines();
// If this operator is on the right leg of a parallel nested join,
// then limit the countOfStreams_ by the number of probes, because
// if we have fewer probes than the number of streams, then some
// streams will be inactive.
if ((partReq_ != NULL) AND
partReq_->isRequirementReplicateNoBroadcast())
{
CostScalar tempCountOfStreams= MINOF(CostScalar(countOfStreams_),
noOfProbes_.getCeiling());
countOfStreams_ = Lng32(tempCountOfStreams.value());
}
// -----------------------------------------------------------------
// When the leaf is pushed down to DP2. Each leaf is responsible
// for grouping in one partition of the table.
// -----------------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = ( noOfProbes_ / countOfStreams_ ).minCsOne();
rowCountPerStream_ = ( child0RowCount_ / countOfStreams_ ).minCsOne();
}
// -----------------------------------------------------------------
// For scalar aggregation, the leaf can do
// full-grouping and give just one row for each table partition on
// each probe, since not much memory is involved. Assume row counts
// are distributed evenly across the all partitions.
// -----------------------------------------------------------------
groupCountPerStream_ = noOfProbesPerStream_;
// Just in case. We want to keep (rowCount >= groupCount).
rowCountPerStream_ = MAXOF(rowCountPerStream_,groupCountPerStream_);
// -----------------------------------------------------------------
// Shouldn't have any having predicates since this is not the final
// consolidator. In some rare cases it is possible to a having pred here
// If there are two nested joins above this partial groupby and there is
// a pred between columns from the outer tables of both NJs then that
// pred can end being pushed into this partial_gb_leaf. E.g. core/test002
// Genesis_10_000222_6892_r3
// -----------------------------------------------------------------
//CMPASSERT(predVis.isEmpty());
myRowCountPerStream_ = groupCountPerStream_;
} // endif(gb_->isNotAPartialGroupBy() OR gb_->isAPartialGroupByRoot())
}
// DIFFICULT case: We have some grouping columns.
else
{
if (pf != NULL)
// ---------------------------------------------------------------
// pf can exist when we're going up the tree. Grab the count of
// partition from there. This is an accurate count.
// ---------------------------------------------------------------
countOfStreams_ = pf->getCountOfPartitions();
else if ((pr != NULL) AND
(pr->getCountOfPartitions() != ANY_NUMBER_OF_PARTITIONS))
countOfStreams_ = pr->getCountOfPartitions();
else
countOfStreams_ = -1;
// If this operator is on the right leg of a parallel nested join,
// then limit the countOfStreams_ by the number of probes, because
// if we have fewer probes than the number of streams, then some
// streams will be inactive.
if ((partReq_ != NULL) AND
partReq_->isRequirementReplicateNoBroadcast())
{
CostScalar tempCountOfStreams= MINOF(CostScalar(countOfStreams_),
noOfProbes_.getCeiling());
countOfStreams_ = Lng32(tempCountOfStreams.value());
}
// -----------------------------------------------------------------
// Required to produce exactly one partition. No choice.
// -----------------------------------------------------------------
if(countOfStreams_ == 1)
{
noOfProbesPerStream_ = noOfProbes_;
groupCountPerStream_ = groupCount_;
// ---------------------------------------------------------------
// When we're going down the tree, the execution requirements
// ensure that this requirement to produce one stream is not
// compatible with a PartialGroupByNonLeaf.
// ---------------------------------------------------------------
if(context_->getPlan()->getPhysicalProperty() == NULL)
{
CMPASSERT(NOT gb_->isAPartialGroupByNonLeaf());
}
// For full group by, just take all the rows and groups.
if(gb_->isNotAPartialGroupBy())
{
rowCountPerStream_ = child0RowCount_;
myRowCountPerStream_ = myRowCount_;
}
else if(gb_->isAPartialGroupByRoot())
// ---------------------------------------------------------------
// For PartialGroupByRoot, some grouping have been done by other
// partial group by operator(s) down the tree (but again, we don't
// know how much).
// There is an upper bound on the no of rows this root gets. Each
// group can be present in the stream produced by each PartialGB
// below, resulting in a total no of groupCount_ * countOfStreams_
// rows. Also, we couldn't have more rows than before any partial
// grouping is done. But we might have as little as groupCount_
// rows if my child is partitioned by my grouping columns.
// ---------------------------------------------------------------
{
// -------------------------------------------------------------
// Added on 1/27/98: Now we know that the row count we directly
// get from my child has been estimated with the partial grouping
// effect.
// -------------------------------------------------------------
// rowCountPerStream_ = groupCount_ * countOfStreams_;
rowCountPerStream_ = child0RowCount_;
myRowCountPerStream_ = myRowCount_;
}
else // a leaf executing in one stream.
{
rowCountPerStream_ = child0RowCount_;
myRowCountPerStream_ = myRowCount_;
}
}
else if (countOfStreams_ >= 2)
// I'm required to produce partitioned streams.
{
if(NOT rpp_->executeInDP2())
{
// -------------------------------------------------------------
// The rowcount for a full groupby is correct. So we are going
// to assume even split across the streams.
// -------------------------------------------------------------
if(gb_->isNotAPartialGroupBy())
{
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
groupCountPerStream_ = groupEstLogProp->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
myRowCountPerStream_ = myLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
groupCountPerStream_ = (groupCount_/countOfStreams_).minCsOne();
myRowCountPerStream_ = (myRowCount_/countOfStreams_).minCsOne();
rowCountPerStream_ = (child0RowCount_/countOfStreams_).minCsOne();
}
}
else
// -------------------------------------------------------------
// We can have a PartialGroupByRoot, PartialGroupByNonLeaf, or
// a PartialGroupByLeaf. In all of these cases, the rowcount
// we have is not a good estimate. They are just the same as
// groupCount, and we can't really do much about it at this point.
// -------------------------------------------------------------
if(gb_->isAPartialGroupByRoot())
{
CostScalar rowCount;
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
groupCountPerStream_ = groupEstLogProp->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
myRowCountPerStream_ = myLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCount = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
groupCountPerStream_ = (groupCount_/countOfStreams_).minCsOne();
myRowCountPerStream_ = (myRowCount_/countOfStreams_).minCsOne();
rowCount = (child0RowCount_/countOfStreams_).minCsOne();
}
// -----------------------------------------------------------
// Assume each of the partial grouping operator has all the
// groups present at each stream.
// -----------------------------------------------------------
// rowCountPerStream_ = MIN_ONE_CS(groupCount_);
rowCountPerStream_ = MAXOF(rowCount, groupCountPerStream_);
}
else
{
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
rowCountPerStream_ = (child0RowCount_/countOfStreams_).minCsOne();
}
groupCountPerStream_ = (groupCount_).minCsOne();
myRowCountPerStream_ = (myRowCount_).minCsOne();
// groupCountPerStream_ should be less or equal to rowCountPerStream_
groupCountPerStream_ = MINOF(groupCountPerStream_, rowCountPerStream_) ;
}
}
else // required to execute in DP2.
{
// -------------------------------------------------------------
// Update on 11/19/97: Check whether we have information on the
// partitioning columns, which is the case when we are going up
// the tree. This information could help us improve our group
// count estimate a lot.
// -------------------------------------------------------------
ValueIdSet partKey;
if (pf != NULL)
partKey = pf->getPartitioningKey();
else
partKey = pr->getPartitioningKey();
if(NOT partKey.isEmpty())
{
// -----------------------------------------------------------
// If partitioning key is a subset of the grouping columns,
// the rows belonging to the same group must be assigned to
// the same partition. Thus, estimate of groupCountPerStream_
// is more like groupCount_ / countOfStream_. In the other
// case, it's more like just groupCount_ if the same group
// can be present in each stream.
// -----------------------------------------------------------
partKey.remove(gb_->groupExpr());
if(partKey.isEmpty())
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
rowCountPerStream_ = (child0RowCount_/countOfStreams_).minCsOne();
groupCountPerStream_ = (groupCount_/countOfStreams_).minCsOne();
myRowCountPerStream_ = (myRowCount_/countOfStreams_).minCsOne();
}
else
{
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
rowCountPerStream_ = (child0RowCount_/countOfStreams_).minCsOne();
}
groupCountPerStream_ = MINOF(groupCount_,rowCountPerStream_);
myRowCountPerStream_ = MINOF(myRowCount_,rowCountPerStream_);
}
}
else
{
// -------------------------------------------------------------
// The rowcounts at the leaf are actually right if full grouping
// can really be done there. Specific implementations are going
// to interpret the group count as such when the operator runs
// in DP2 and then considers whether it has the memory to do a
// full grouping.
// -------------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
groupCountPerStream_ = groupEstLogProp->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
myRowCountPerStream_ = myLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
rowCountPerStream_ = (child0RowCount_/countOfStreams_).minCsOne();
groupCountPerStream_ = (groupCount_/countOfStreams_).minCsOne();
myRowCountPerStream_ = (myRowCount_/countOfStreams_).minCsOne();
}
}
}
} // endif(countOfStreams_ >= 2)
else
// -------------------------------------------------------------------
// On the way down and no partitioning requirement specified, or the
// partitioning requirement did not specify a number of partitions.
// The operator can choose its own degree of parallelism.
// -------------------------------------------------------------------
{
if(NOT rpp_->executeInDP2())
{
// must underestimate, since we are on our way down
countOfStreams_ = rpp_->getCountOfPipelines();
// ---------------------------------------------------------------
// The rowcount for a full groupby is correct. So we are going to
// assume even split across the streams.
// ---------------------------------------------------------------
if(gb_->isNotAPartialGroupBy())
{
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
groupCountPerStream_ = groupEstLogProp->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
myRowCountPerStream_ = myLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
groupCountPerStream_ = (groupCount_/countOfStreams_).minCsOne();
myRowCountPerStream_ = (myRowCount_/countOfStreams_).minCsOne();
rowCountPerStream_ = (child0RowCount_/countOfStreams_).minCsOne();
}
}
else
// ---------------------------------------------------------------
// We can have a PartialGroupByRoot, PartialGroupByNonLeaf, or
// a PartialGroupByLeaf. In all of these cases, the rowcount
// we have is not a good estimate. They are just the same as
// groupCount, and we can't really do much about it at this point.
// ---------------------------------------------------------------
if(gb_->isAPartialGroupByRoot())
{
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
groupCountPerStream_ = groupEstLogProp->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
myRowCountPerStream_ = myLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
groupCountPerStream_ = (groupCount_/countOfStreams_).minCsOne();
myRowCountPerStream_ = (myRowCount_/countOfStreams_).minCsOne();
}
// -------------------------------------------------------------
// Assume each of the partial grouping operator has all the
// groups present at each stream.
// -------------------------------------------------------------
rowCountPerStream_ = (groupCount_).minCsOne();
} // gb_->isAPartialGroupByRoot
else
{
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
rowCountPerStream_ = (child0RowCount_/countOfStreams_).minCsOne();
}
groupCountPerStream_ = (groupCount_).minCsOne();
myRowCountPerStream_ = (myRowCount_).minCsOne();
// groupCountPerStream_ should be less or equal to rowCountPerStream_
groupCountPerStream_ = MINOF(groupCountPerStream_, rowCountPerStream_) ;
}
}
else
{
// ---------------------------------------------------------------
// We're going down, we were not given a part. requirement,
// and we are in DP2. Any group by operator pushed
// down to DP2 is just going to follow whichever partitioning
// is inherent in the physical table. So, just guess that the
// table is partitioned max pipelines ways. On the way
// back up we will have the synthesized partitioning function
// and so we will not come here.
//
// ---------------------------------------------------------------
countOfStreams_ = rpp_->getCountOfPipelines();
// ---------------------------------------------------------------
// The rowcounts at the leaf are actually right if full grouping
// can really be done there. Specific implementations are going
// to interpret the group count as such when the operator runs
// in DP2 and then considers whether it has the memory to do a
// full grouping.
// ---------------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = inLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_);
groupCountPerStream_ = groupEstLogProp->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
myRowCountPerStream_ = myLogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
// groupAttr are passed to compute the columns of partitioning key
// which belong to this operator. For group by, the subtree of this
// node is same as that of the child
// so we shall pass this nodes group attributes for the child too
rowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
ga_,
countOfAvailableCPUs_);
}
else
{
noOfProbesPerStream_ = (noOfProbes_/countOfStreams_).minCsOne();
groupCountPerStream_ = (groupCount_/countOfStreams_).minCsOne();
myRowCountPerStream_ = (myRowCount_/countOfStreams_).minCsOne();
rowCountPerStream_ = (child0RowCount_/countOfStreams_).minCsOne();
}
}
} // end if on way down and no number of partitions requirement
} // endif(grbyVis.isEmpty())
#ifndef NDEBUG
// debug
CostScalar delta = rowCountPerStream_ - groupCountPerStream_;
if ((delta < 0) && (CURRSTMT_OPTGLOBALS->warningGiven == FALSE))
{
*CmpCommon::diags()
<< DgSqlCode(CMP_OPT_WARN_FROM_DCMPASSERT)
<< DgString0("delta < 0");
CURRSTMT_OPTGLOBALS->warningGiven = TRUE;
}
#endif
// recover in release
if (groupCountPerStream_ > rowCountPerStream_)
groupCountPerStream_ = rowCountPerStream_;
} // CostMethodGroupByAgg::estimateDegOfParallelism().
void CostMethodGroupByAgg::cleanUp()
{
child0LogProp_ = 0;
myIntLogProp_ = 0;
CostMethod::cleanUp();
}
//<pb>
// ----QUICKSEARCH FOR SGB................................................
/**********************************************************************/
/* */
/* CostMethodSortGroupBy */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodSortGroupBy::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodSortGroupBy::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// CostScalars to be computed.
// ---------------------------------------------------------------------
CostScalar cpuFR(csZero), cpuLR(csZero), mem(csZero);
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
if (CmpCommon::getDefault(COMP_BOOL_86) == DF_ON)
{
// reset cpuCopyCostRow similar to hash grpby
cpuCostReturnRow_ = CostPrimitives::cpuCostForCopyRow(
groupKeyLength_ + aggregateLength_);
}
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// Added on 7/16/97: If we're on our way down the tree and this group
// by is being considered for execution in DP2, generate a zero cost
// object first and come back to cost it later when we're on our way up.
// Set the count of streams to an invalid value (0) to force us to
// recost on the way back up.
// ---------------------------------------------------------------------
if(rpp_->executeInDP2() AND
(NOT context_->getPlan()->getPhysicalProperty()))
{
countOfStreams = 0;
return generateZeroCostObject();
}
// ---------------------------------------------------------------------
// Make sure rowcount is at least group count to prevent absurdity in
// results.
// ---------------------------------------------------------------------
rowCountPerStream_ = MAXOF(rowCountPerStream_,groupCountPerStream_);
// ---------------------------------------------------------------------
// Per probe initialization.
// ---------------------------------------------------------------------
cpuFR += cpuCostPerProbeInit_;
cpuLR += cpuCostPerProbeInit_ * noOfProbesPerStream_;
// ---------------------------------------------------------------------
// Cost to initialize a new group.
// ---------------------------------------------------------------------
cpuFR += cpuCostInitNewGroup_;
cpuLR += cpuCostInitNewGroup_ * groupCountPerStream_;
// ---------------------------------------------------------------------
// CPU cost for comparing the group keys.
// ---------------------------------------------------------------------
cpuFR += cpuCostCompareGroupKeys_ *
MINOF(child0RowCount_ / groupCount_,rowCountPerStream_);
cpuLR += cpuCostCompareGroupKeys_ * rowCountPerStream_;
// ---------------------------------------------------------------------
// CPU cost for aggregating a row with an existing group.
// ---------------------------------------------------------------------
cpuFR += cpuCostAggrRowToGroup_ * MINOF(child0RowCount_ / groupCount_,
rowCountPerStream_ - groupCountPerStream_);
cpuLR +=
cpuCostAggrRowToGroup_ * (rowCountPerStream_ - groupCountPerStream_);
// ---------------------------------------------------------------------
// CPU cost for evaluating the having predicate on a group.
// ---------------------------------------------------------------------
cpuFR += cpuCostEvalHavingPred_ * MINOF(groupCount_ / myRowCount_,
groupCountPerStream_);
cpuLR += cpuCostEvalHavingPred_ * groupCountPerStream_;
// ---------------------------------------------------------------------
// CPU cost to return a qualified row.
// ---------------------------------------------------------------------
cpuFR += cpuCostReturnRow_;
cpuLR += cpuCostReturnRow_ * myRowCountPerStream_;
// ---------------------------------------------------------------------
// Buffers initially allocated to keep the result.
// ---------------------------------------------------------------------
mem = CostScalar(bufferCount_ * bufferSize_);
// ---------------------------------------------------------------------
// Synthesize the simple cost vectors.
// ---------------------------------------------------------------------
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
CostScalar cpuForcvFR;
cpuForcvFR = cpuFR/noOfProbesPerStream_ * ff_cpu;
SimpleCostVector cvFR (
cpuForcvFR,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
SimpleCostVector cvLR (
cpuLR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"SORTGROUPBY::computeOperatorCost()\n");
fprintf(pfp,"child0RowCount=%g,groupCount=%g,myRowCount=%g\n",
child0RowCount_.value(),groupCount_.value(),myRowCount_.value());
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return the cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
// If this is a partial Group By leaf in an ESP adjust it's cost
GroupByAgg * groupByNode = (GroupByAgg *) op;
PhysicalProperty * sppForMe = (PhysicalProperty *) myContext->
getPlan()->getPhysicalProperty();
if(groupByNode->isAPartialGroupByLeaf() &&
((sppForMe && sppForMe->executeInESPOnly()) ||
(CmpCommon::getDefault(COMP_BOOL_186) == DF_ON)))
{
// don't adjust if Group Columns contain partition columns.
const PartitioningFunction* const myPartFunc =
sppForMe->getPartitioningFunction();
ValueIdSet myPartKey = myPartFunc->getPartitioningKey();
ValueIdSet myGroupingColumns = groupByNode->groupExpr();
NABoolean myGroupingMatchesPartitioning = FALSE;
if (myPartKey.entries() &&
myGroupingColumns.contains(myPartKey))
myGroupingMatchesPartitioning = TRUE;
if (!myGroupingMatchesPartitioning)
{
CostScalar grpByAdjFactor = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(ROBUST_PAR_GRPBY_LEAF_FCTR);
cvLR *= grpByAdjFactor;
cvFR *= grpByAdjFactor;
}
}
Cost *costPtr = new STMTHEAP Cost( &cvFR,
&cvLR,
NULL,
cpuCount,
fragmentsPerCPU
);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodSortGroupBy::computeOperatorCostInternal().
//<pb>
// ----QUICKSEARCH FOR HGB................................................
/**********************************************************************/
/* */
/* CostMethodHashGroupBy */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodHashGroupBy::cacheParameters().
// -----------------------------------------------------------------------
void CostMethodHashGroupBy::cacheParameters(RelExpr* op,
const Context* myContext)
{
CostMethodGroupByAgg::cacheParameters(op,myContext);
// Cost to compute the hash value for a row.
cpuCostHashRow_ = CostPrimitives::cpuCostForHash(gb_->groupExpr());
// HGB needs to copy the row from local buffer to result buffer.
cpuCostReturnRow_ = CostPrimitives::cpuCostForCopyRow(
groupKeyLength_ + aggregateLength_);
// Besides regular stuffs, we also have to insert the row to the chain.
cpuCostInitNewGroup_ += cpuCostInsertRowToChain_;
// ---------------------------------------------------------------------
// Since partial groups may result from spilling, we might have to aggr
// a partial group into another in subsequent passes. The cost to aggr
// a row to a group and a partial group to another may be different. We
// charge the same cost for the time being.
// ---------------------------------------------------------------------
cpuCostAggrGroupToGroup_ = cpuCostAggrRowToGroup_;
// Length of the group with aggregates and the hash table overhead.
extGroupLength_ =
groupKeyLength_ + aggregateLength_ + hashedRowOverhead_;
// ---------------------------------------------------------------------
// Added on 1/27/98: Now groupCount_ has been estimated with some actual
// partial grouping effect. However, it has been estimated assuming the
// table has only one partition and the partial grouping is done in a
// single DP2. Here, we need to turn this groupCount_ back to what it
// was before, so that the original logic in computePartialGroupByLeafCost()
// can correctly handle it. The formulae for doing so are given below:
//
// Gfull = no of full groups. also final value of groupCount_.
//
// Gpart = no of partial groups as estimated by new EstLogProp
// code. ie. assuming partial grouping in ONE DP2.
// also the initial value of groupCount_.
//
// R = total no of rows in the table (all partitions).
//
// Mgp = memory need to store a group.
//
// Mdp2 = max memory DP2 can allow this partial groupby to use.
//
// Gdp2 = Mdp2 / Mgp (no of groups accommodated in one DP2)
//
// Gpart = Gdp2 + (R - Gdp2 * (R / Gfull)).
//
// ===> Gfull = (R * Gdp2) / (R + Gdp2 - Gpart)
// ---------------------------------------------------------------------
if( myContext->getReqdPhysicalProperty()->executeInDP2() &&
(CmpCommon::getDefault(COMP_BOOL_52) == DF_ON)
)
{
CostScalar Gdp2 =
(memoryLimitInDP2_ * csOneKiloBytes * csOneKiloBytes / extGroupLength_);
// case where DP2 cannot accommodate all groups.
if ( Gdp2 < groupCount_ )
{
// Changed the order of arithmetic operators in the following expression
// Initially we were first adding Gdp2 to child0RowCount_ and then
// subtracting groupCount_ from the result. If the difference
// was smaller than COSTSCALAR_EPSILON, then the result returned would be zero.
// This was causing an assertion later while estimating parallelism.
// This is not the required behaviour in this case. As a temporary fix
// I first subtract the groupCount_ and then add Gdp2 to it. For a better
// fix we should revisit, costScalar subtraction to see, why is there a
// need to return zero, if the difference is smaller than epsilon.
CostScalar denominator =
(child0RowCount_ - groupCount_ + Gdp2 ).minCsOne();
// MIN_ONE_CS(child0RowCount_ + Gdp2 - groupCount_);
groupCount_ = child0RowCount_ / denominator * Gdp2;
}
}
} // CostMethodHashGroupBy::cacheParameters().
//<pb>
// -----------------------------------------------------------------------
// CostMethodHashGroupBy::deriveParameters().
//
// Assume that both cacheParameters() and estimateDegreeOfParallelism()
// have been called. Derive three parameters: rowCount_,groupCount_ and
// noOfClusters_, which together forms an initial working set of
// parameters for computePassCost().
// -----------------------------------------------------------------------
void CostMethodHashGroupBy::deriveParameters()
{
// ---------------------------------------------------------------------
// These metrics are computed on a per-stream per-probe basis.
// ---------------------------------------------------------------------
CostScalar rowCount =
(rowCountPerStream_ / noOfProbesPerStream_).minCsOne();
CostScalar groupCount =
(groupCountPerStream_ / noOfProbesPerStream_).minCsOne();
CostScalar groupedTableSize = groupCount / csOneKiloBytes * extGroupLength_;
// No memory limit or whole grouped table enough to fit in main memory.
// Old behaviour
if ( (CmpCommon::getDefault(COMP_BOOL_52) == DF_ON) &&
(NOT isBMO_ || groupedTableSize <= memoryLimit_)
)
{
noOfClustersToBeAllocated_ = 1;
}
else
{
// Use estimates to compute how many clusters we allocate.
noOfClustersToBeAllocated_ =
computeCountOfClusters(memoryLimit_,groupedTableSize);
// We should have allocated > 1 clusters if table doesn't fit.
if ( CmpCommon::getDefault(COMP_BOOL_52) == DF_ON)
CMPASSERT(noOfClustersToBeAllocated_ > 1);
}
// To begin with, there is one cluster with all rows in the table.
noOfClustersToBeProcessed_ = 1;
groupCountPerCluster_ = groupCount;
rowCountPerCluster_ = rowCount;
} // CostMethodHashGroupBy::deriveParameters().
//<pb>
//==============================================================================
// Compute cost for doing hash grouping without overflow. This is the
// algorithm currently used for the executor Hash Group By operator when it
// executes in DP2.
// Executor behavior from R2.2 has changed. Now Partial_Hash_Groupby_leaf can
// happen in ESP. Partial Groupby Leaf never overflows, its job is to reduce
// the input going to the root. It uses fixed size memory:
// 1. 100 MB if in an ESP, controlled by EXE_MEMORY_FOR_PARTIALHGB_IN_MB.
// 2. 1000 groups if in DP2, controlled by MAX_DP2_HASHBY_GROUP.
//
// We can keep on allocating buffers to accommodate new groups as far as
// all the memory available has not been used up. When we see a new group
// but have no more space for it, the row is simply returned. Thus, only
// partial groups may result.
//
// Input:
// none
//
// Output:
// cpuFR -- CPU usage to produce first row after blocking phase
// completes.
// cpuLR -- CPU usage to produce all rows after blocking phase
// completes.
// cpuBK -- CPU usage during blocking phase.
// groupingFactor -- Percentage of groups which fit completely in memory.
//
// Return:
// none
//
//==============================================================================
void
CostMethodHashGroupBy::computePartialGroupByLeafCost(
CostScalar& cpuFR,
CostScalar& cpuLR,
CostScalar& cpuBK,
CostScalar& groupingFactor)
{
CostScalar rowCount = rowCountPerStream_;
CostScalar groupCount = groupCountPerStream_;
//--------------------------------------
// Guard against potential abnormalies.
//--------------------------------------
rowCount = (rowCount).minCsOne();
groupCount = (groupCount).minCsOne();
//-----------------------------------------------------------------
// Number of groups the dp2 groupby can accomodate is at most 1000;
// The CQD MAX_DP2_HASHBY_GROUPS has this as the default value.
//
// how many rows are consumed by the group by operator depends on UEC,
// skew etc.
// For now we assume uniform distribution among the distinct values given
// by the UEC
//-----------------------------------------------------------------
CostScalar groupCountInMemory = csZero;
if(rpp_->executeInDP2())
{
groupCountInMemory =
ActiveSchemaDB()->getDefaults().getAsLong(MAX_DP2_HASHBY_GROUPS);
}
else
{
// get Esp groupby memory limit (default 100MB).
CostScalar memorySizeForEsp = ActiveSchemaDB()->
getDefaults().getAsLong(EXE_MEMORY_FOR_PARTIALHGB_IN_MB);
// convert MB into bytes, divide by groupLength to get partial groupCount
groupCountInMemory = (memorySizeForEsp * 1048576) / extGroupLength_;
}
groupCountInMemory =
MINOF(groupCountInMemory,groupCount);
mem_ =
groupCountInMemory * extGroupLength_ / csOneKiloBytes;
//fudge factor for CPU
const CostScalar ff_cpu =
CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
//-----------------------------------------------
// The hash table is probed for every input row.
//-----------------------------------------------
CostScalar cpu = cpuCostPositionHashTableCursor_ * rowCount * ff_cpu;
//---------------------------------------------------------------
// Charge an initialize new group cost for each group in memory.
//---------------------------------------------------------------
cpu += cpuCostInitNewGroup_ * groupCountInMemory * ff_cpu;
//-----------------------------------------
// Can't have more groups than input rows.
//-----------------------------------------
CMPASSERT(rowCount >= groupCount);
//----------------------------------
// Average number of rows per group.
//----------------------------------
CostScalar rowsPerGroup = (rowCount / groupCount);
//---------------------------------------------------------------------------
// Aggregation is done only for rows whose group is in memory.
//
// Note that the cost for the first row of a group is reflected in the group
// initialization cost while all subsequent rows for a group are actually
// aggregated into the group. Hence the term (rowsPerGroup - 1.).
//---------------------------------------------------------------------------
cpu += cpuCostAggrRowToGroup_ * (rowsPerGroup - csOne) * groupCountInMemory
* ff_cpu;
//------------------------------------------------
// Cost to compute hash value for each input row.
//------------------------------------------------
cpu += cpuCostHashRow_ * rowCount * ff_cpu;
//---------------------------------------------
// Fraction of groups which are fully grouped.
//---------------------------------------------
groupingFactor = (groupCountInMemory / groupCount);
CostScalar rowsConsumedByPartialGB = (groupingFactor * rowCount);
//-------------------------------------------------------------------------
// Since we are executing in DP2, once overflow occurs, we start returning
// rows belonging to not yet formed groups.
//
// Thus, all work done up to the first overflow belongs to
// blocking usage. All subsequent work belongs to last row usage.
//-------------------------------------------------------------------------
cpuFR = groupingFactor * cpu;
//------------------------------------------------------------------------
// we compute the blocking cost in the following way:
// number of rows preBlocking that need to be processed before the
// first row is
// returned is at least groupCountInMemory. that is groupCountIMemory < NR.
// Also it must be numRowsPreBlocking < rowsConsumedByPartialGB.
// We take the mid-point as a
// heuristic.
// We compute the following costs for each row
// 1. probing cost
// 2. group initialization cost
// 3. aggregate evaluation cost
//
// Old behaviour - blocking cost is zero - could be obtained by turning on
// the CQD
//-------------------------------------------------------------------------
CostScalar numRowsPreBlocking;
if (NOT (groupingFactor.isLessThanOne()))
{
numRowsPreBlocking = rowsConsumedByPartialGB;
}
else
{
numRowsPreBlocking = groupCountInMemory +
(rowsConsumedByPartialGB - groupCountInMemory) /2;
// this is roughly equal to rowsConsumedByPartialGB/2 + 500
}
//------------------------------------------------------------------
// blocking cost is zero, if the old behaviour is desired OR
// aggregate expressions do not exist
//------------------------------------------------------------------
if ( CmpCommon::getDefault(COMP_BOOL_52) == DF_ON ||
gb_->aggregateExpr().isEmpty())
{
cpuBK = csZero;
cpuLR = cpu;
}
else
{
// blocking cost computation depends on the order of rows of the input
// that is how quickly we fill up 1000 groups in the worst case.
// this is not a concern if the grouping factor is 1
//
// if number of groups is more than 1000, we assume that we need to
// hash rowsConsumedByPartialGB/2 times before the groups are full.
// it follows that the last row cost needs to get hashing cost for the
// remaining hash cost of rowsConsumedByPartialGB/2 rows.
cpuBK = ((cpuCostInitNewGroup_ * groupCountInMemory)+
(cpuCostHashRow_ +
cpuCostAggrRowToGroup_ +
cpuCostPositionHashTableCursor_) *
numRowsPreBlocking )
* ff_cpu ;
// give more weight to plans where grouping Factor is 1 or is more
// than 70%.
if (NOT(groupingFactor.isLessThanOne())) // Is it one?
cpuBK = cpuBK/2;
else if (groupingFactor >= CostScalar(0.7))
cpuBK = cpuBK/1.5;
}
if ( CmpCommon::getDefault(COMP_BOOL_52) == DF_OFF &&
(NOT (gb_->aggregateExpr().isEmpty()) ))
{
//-------------------------------------------------------------------
// Cost to return all output rows. Some of these are grouped rows and
// some are un-grouped.
//-------------------------------------------------------------------
cpuLR += cpuCostReturnRow_ * ff_cpu *
(groupCountInMemory + rowCount - rowsConsumedByPartialGB);
if ( CmpCommon::getDefault(COMP_BOOL_90) == DF_OFF )
{
if (groupingFactor < CostScalar(1) )
cpuLR += (cpuCostHashRow_ +
cpuCostAggrRowToGroup_ +
cpuCostPositionHashTableCursor_) *
rowsConsumedByPartialGB/2
* ff_cpu ;
}
else
{
if (groupingFactor.isLessThanOne())
{
cpuLR += (csOne - groupingFactor) * cpuCostHashRow_
* rowCount * ff_cpu;
cpuLR += cpuCostAggrRowToGroup_ * rowsConsumedByPartialGB/2 * ff_cpu;
}
}
//---------------------------------------------------------------------
// First row cost involves simply the cost to return one row after all
// blocking activity has occurred.
//---------------------------------------------------------------------
}
cpuFR = cpuCostReturnRow_ * ff_cpu;
if (cpuFR > cpuLR)
cpuFR = cpuLR;
} // CostMethodHashGroupBy::computePartialGroupByLeafCost().
//<pb>
//==============================================================================
// The classic Hash GroupBy algorithm may take several passes. In each pass,
// if memory fills up, a cluster is selected for spilling to disk. The
// spilled cluster is only partially grouped, so it must be grouped again in a
// subsequent pass.
//
// This member function computes the costs associated with a single pass of the
// Hash GroupBy algorithm including the cost (if any) to read previously spilled
// clusters from disk and to write spilled clusters to disk.
//
// NOTE: If this function returns TRUE, it will change the private data members
// noOfClustersToProcessed_, groupCountPerClusters_ and
// rowCountPerClusters_ to reflect the new numbers for the next pass.
//
// Input:
// isFirstPass -- TRUE for first call to this routine; FALSE otherwise.
// When TRUE, assume input is un-grouped rows. Otherwise,
// assume input rows are partially grouped already.
//
// Output:
// cvPassCurr -- Cost vector representing CPU and IO resources used for
// current pass.
//
// isRowProduced -- TRUE if this pass produces an output row; FALSE otherwise.
//
// Return:
// TRUE if current pass had to do overflow spilling (thus requiring a
// subsequent call to this routine); FALSE otherwise.
//
//==============================================================================
NABoolean
CostMethodHashGroupBy::computePassCost(NABoolean isFirstPass,
SimpleCostVector& cvPassCurr,
NABoolean& isRowProduced)
{
//---------------------------------------------------------------------
// CostScalars to be computed. We first compute the cost of processing
// one previously spilled cluster and scale the cost up to arrive at
// the total cost for all clusters.
//---------------------------------------------------------------------
CostScalar cpu(csZero), ioSeek(csZero), ioByte(csZero); //j mem(csZero), disk(csZero);
cvPassCurr.reset();
cvPassCurr.setNumProbes(csOne);
//--------------------------------------------------------
// Common calculations.
//--------------------------------------------------------
CostScalar extGroupLengthInKB = CostScalar(extGroupLength_) / csOneKiloBytes;
//--------------------------------------------------------
// A group must be accommodated in a buffer. Otherwise...
//--------------------------------------------------------
CMPASSERT( extGroupLengthInKB <= bufferSize_ );
//---------------------------------------------------------------------
// The contents of EACH cluster spilled over from a previous pass are
// are going to be divided into noOfClustersAllocated_ and processed.
// In order to avoid confusion, we name the metrics of each previously
// spilled cluster without the "PerCluster" suffix. The suffix is saved
// to mean those metrics of a cluster at this pass.
//---------------------------------------------------------------------
//----------------------------------------------------
// "PerCluster" metrics of previous pass get renamed.
//----------------------------------------------------
CostScalar rowCount = rowCountPerCluster_;
CostScalar groupCount = groupCountPerCluster_;
//-------------------------------------
// Size of the table if fully grouped.
//-------------------------------------
CostScalar groupedTableSize = groupCount * extGroupLengthInKB;
//--------------------------------------------------------------------
// Unless we are working on the first pass, we need to read back from
// disk each previously-spilled cluster.
//--------------------------------------------------------------------
if(NOT isFirstPass)
{
ioByte = rowCount * extGroupLengthInKB;
ioSeek = ioByte / bufferSize_;
}
//--------------------------------------------------------------------
// CPU costs involved in groupby processing, considering rows in all
// clusters of this pass.
//
// 1. The hash value of a row is used to determine the cluster a row
// belongs to.
// 2. The hash table at that cluster is probed to see whether the row
// is part of an existing group in the buffer.
// 3. If yes, the row is aggregated into the group. Otherwise, we copy
// the grouping columns of the row to the buffer, initialize the
// aggregates of that new group, and insert it into the hash table.
//--------------------------------------------------------------------
//-----------------------------------------------
// The hash table is probed for every input row.
//-----------------------------------------------
cpu += cpuCostPositionHashTableCursor_ * rowCount;
//----------------------------------------------------------------
// Spilling causes partial groups. Thus, this is under-estimated.
//----------------------------------------------------------------
cpu += cpuCostInitNewGroup_ * groupCount;
//-----------------------------------------------
// Cost to aggregate a row to an existing group.
//-----------------------------------------------
const CostScalar & cpuCostAggregation =
(isFirstPass ? cpuCostAggrRowToGroup_ : cpuCostAggrGroupToGroup_);
//-----------------------------------------------------------
// This is over-estimated as spilling causes partial groups.
//-----------------------------------------------------------
CMPASSERT(rowCount >= groupCount);
cpu += cpuCostAggregation * (rowCount - groupCount);
//-----------------------------------------------------------------------
// Determine whether spilling has occurred.
//
// No more spilling. We have a quick way out. Just scale up the CPU cost
// by the number of clusters processed at this pass and return FALSE.
//-----------------------------------------------------------------------
if((NOT isBMO_) OR (groupedTableSize <= memoryLimit_))
{
//----------------------------------------------------
// Since no spilling occurs, we begin returning rows.
//----------------------------------------------------
isRowProduced = TRUE;
//------------------------------------------------------------------------
// Set resources used for this pass scaled up for the number of clusters.
//------------------------------------------------------------------------
cvPassCurr.setInstrToCPUTime(cpu * noOfClustersToBeProcessed_);
cvPassCurr.addKBytesToIOTime(ioByte * noOfClustersToBeProcessed_);
cvPassCurr.addSeeksToIOTime(ioSeek * noOfClustersToBeProcessed_);
cvPassCurr.addNumLocalToMSGTime(ioSeek * noOfClustersToBeProcessed_);
cvPassCurr.addKBLocalToMSGTime(ioByte * noOfClustersToBeProcessed_);
//j cvPassCurr.setNormalMemory(groupedTableSize);
//j cvPassCurr.setPersistentMemory(groupedTableSize);
//--------------------------------
// No more spilling in this pass.
//--------------------------------
return FALSE;
}
//------------------------------------
// "PerCluster" metrics of this pass.
//------------------------------------
CostScalar rowCountPerCluster =
(rowCount / noOfClustersToBeAllocated_).minCsOne();
CostScalar groupCountPerCluster =
(groupCount / noOfClustersToBeAllocated_).minCsOne();
//----------------------------------------------------
// Size of a cluster if grouping has been fully done.
//----------------------------------------------------
CostScalar clusterSize = groupCountPerCluster * extGroupLengthInKB;
//--------------------------------------------------------------------
// Must have allocated more than one cluster if spilling is expected.
//--------------------------------------------------------------------
CMPASSERT(noOfClustersToBeAllocated_ > 1);
//----------------------------------------------------------------------
// The 1st buffer of each cluster has to remain in memory all the time.
//----------------------------------------------------------------------
CostScalar memoryFor1stBufferOfClusters =
bufferSize_ * noOfClustersToBeAllocated_;
//-------------------------------------------------------
// Memory must accommodate one buffer from each cluster.
//-------------------------------------------------------
CMPASSERT(memoryFor1stBufferOfClusters <= memoryLimit_);
//----------------------------------------------------------------------
// Memory left for the first cluster. Each cluster takes up one buffer.
//----------------------------------------------------------------------
CostScalar memoryLeft =
CostScalar(memoryLimit_) - memoryFor1stBufferOfClusters;
//---------------------------------------------------------------------
// Actually, (clusterSize > bufferSize_) is always true because:
//
// (clusterSize * noOfClustersAllocated_) == groupedTableSize >
// memoryLimit_ >= memoryFor1stBufferOfClusters ==
// (bufferSize_ * noOfClustersAllocated_).
//
// But in case some kind of truncations or floating point manipulation
// produces an error big enough to make this untrue...
//---------------------------------------------------------------------
clusterSize = MAXOF(clusterSize,bufferSize_+.1);
//----------------------------------------------
// Number of clusters which can stay in memory.
//----------------------------------------------
Lng32 noOfClustersInMemory = (Lng32)
(memoryLeft / (clusterSize - bufferSize_)).getFloor().value();
//----------------------------------------------------------------------
// for the clusters that stay in memory, calculate average chain length
// in the event of collisions; if the chain is long, cpuCostInsertRowToChain_
// needs to be adjusted appropriately takig into account datatype of
// grouping columns, especially for char and varchar types.
//
// We ignore memory pressure, executor assumes a 10MB available memory and
// 10% of this is used for constructing hash entries in the hash table;
// Each entry takes 4 bytes; consequently, there are about 250,000 entries per
// cluster; then average chain length is groupcountPerCluster/ 250000
// this is not quite accurate, if we do not have good stats or good
// cardinality estimates (UEC of grouping columns).
// We ignore these scenarios for now
//------------------------------------------------------------------------
if ( CmpCommon::getDefault(COMP_BOOL_52) == DF_OFF)
{
CostScalar averageChainLength = (groupCountPerCluster/
ActiveSchemaDB()->getDefaults()
.getAsLong(MAX_HEADER_ENTREIS_PER_HASH_TABLE)).getFloor();
CostScalar costToInsert = calculateCostToInsertIntoChain
(averageChainLength);
cpu += costToInsert * noOfClustersInMemory;
}
//------------------------------------------------------------
// Number of clusters which spills over to disk in this pass.
//------------------------------------------------------------
Lng32 noOfClustersSpilled =
noOfClustersToBeAllocated_ - noOfClustersInMemory;
// -----------------------------------------------------------------------
// Groups in spilled clusters are only partial groups. Thus, they take
// up more disk space and incur more disk I/O than that estimated from
// their sizes when fully grouped. In the worst though unlikely case,
// all the rows in the spilled cluster could remain ungrouped. It might
// also happen that the cluster could have been fully grouped if the
// rows are sorted by the grouping columns (in which case we should be
// doing sort group by instead). So the number of rows written to disk for
// each of these spilled clusters could be anywhere between the extremes
// of groupCountPerCluster and rowCountPerCluster.
//
// In order to account for this, we introduce a fludge factor we call
// groupingFactorForSpilledClusters_ which can range from 0 to 1. And
// the number of rows in a spilled cluster is determined by the formula:
//
// rowCountPerCluster +
// groupingFactorForSpilledClusters_ *
// (groupCountPerCluster - rowCountPerCluster).
//
// which can lie between groupCountPerCluster and rowCountPerCluster.
// -----------------------------------------------------------------------
CostScalar rowCountPerSpilledCluster =
rowCountPerCluster + (groupCountPerCluster - rowCountPerCluster) *
groupingFactorForSpilledClusters_;
//-----------------------------------------------------------------------
// Re-estimate spilled cluster size after adjusted for partial grouping.
//-----------------------------------------------------------------------
CostScalar spilledClusterSize =
rowCountPerSpilledCluster * extGroupLengthInKB;
//---------------------------------------
// I/O for writing the spilled clusters.
//---------------------------------------
//j disk = spilledClusterSize * noOfClustersSpilled;
ioByte = spilledClusterSize * noOfClustersSpilled;
ioSeek = ioByte / bufferSize_;
//----------------------------------------------------------------------------
// Now, the whole thing is done for *each* of the previously spilled clusters
// which is stored as noOfClustersToBeProcessed_ before this function has been
// called. We need to scale up the cost we have computed so far by that
// factor.
//----------------------------------------------------------------------------
cvPassCurr.setInstrToCPUTime(cpu * noOfClustersToBeProcessed_);
cvPassCurr.addKBytesToIOTime(ioByte * noOfClustersToBeProcessed_);
cvPassCurr.addSeeksToIOTime(ioSeek * noOfClustersToBeProcessed_);
cvPassCurr.addNumLocalToMSGTime(ioSeek * noOfClustersToBeProcessed_);
cvPassCurr.addKBLocalToMSGTime(ioByte * noOfClustersToBeProcessed_);
//j cvPassCurr.setDiskUsage(disk);
//j cvPassCurr.setNormalMemory(memoryLimit_);
//j cvPassCurr.setPersistentMemory(memoryLimit_);
//---------------------------------------------------------------------
// If there is a un-spilled cluster in this pass, assume the first row
// is produced. But the row is produced only after we finish all the
// processing for the first cluster processed.
//---------------------------------------------------------------------
isRowProduced = (noOfClustersInMemory > 0);
//------------------------------------------------------------------------
// Finally, we need to prepare new values of noOfClustersToBeProcessed_,
// rowCountPerCluster_ and groupCountPerCluster_ for the next pass if
// we have spilling in this pass.
//
// This needs explanation. *Each* previously-spilled cluster which is
// processed in this pass generates a number of spilled clusters stored
// in noOfClustersSpilled.
//
// Since we process a total of noOfClustersToBeProcessed_ previously-
// spilled clusters in this pass, total number of spilled clusters created
// for the next pass = noOfClustersToBeProcessed_ * noOfClustersSpilled,
// and this number becomes noOfClustersToBeProcessed_ for the next pass.
//------------------------------------------------------------------------
groupCountPerCluster_ = groupCountPerCluster;
rowCountPerCluster_ = rowCountPerSpilledCluster;
// this check was added to prevent overflowing. 11/06/00
if ( noOfClustersToBeProcessed_ > INT_MAX/MIN_ONE(noOfClustersSpilled) )
noOfClustersToBeProcessed_ = INT_MAX;
else
noOfClustersToBeProcessed_ *= noOfClustersSpilled;
return TRUE;
} // CostMethodHashGroupBy::computePassCost()
//------------------------------------------------------------------
// This method computes cost of chains; assume on an average we
// check half of the chain
//------------------------------------------------------------------
CostScalar CostMethodHashGroupBy::calculateCostToInsertIntoChain
(CostScalar &averageChainLength)
{
if (averageChainLength < 2 )
return CostScalar (0);
return averageChainLength * groupKeyLength_/2 * cpuCostCompareGroupKeys_;
} // CostMethodHashGroupBy::calculateCostToInsertIntoChain()
//<pb>
//==============================================================================
// Compute number of clusters used by a Hash GroupBy. This is based on the
// memory limit, the estimated grouped-table size and the buffer size (as stored
// in the private section of the class).
//
// Input:
// memoryLimit -- Amount of main memory available to Hash GroupBy.
//
// tableSize -- Size of input table for Hash GroupBy.
//
// Output:
// none
//
// Return:
// Number of clusters used by Hash GroupBy algorithm.
//
//==============================================================================
Lng32
CostMethodHashGroupBy::computeCountOfClusters( const CostScalar & memoryLimit,
const CostScalar & tableSize )
{
CostScalar clusters;
Lng32 maxClusters;
if ( CmpCommon::getDefault(COMP_BOOL_52) == DF_ON)
{
clusters = (tableSize / memoryLimit);
maxClusters = (Lng32)((memoryLimit / bufferSize_).getFloor().value());
if (clusters > double(maxClusters))
return maxClusters;
}
else
{
// if in dp2, the number of clusters is 1
if (rpp_->executeInDP2())
{
return 1;
}
//---------------------------------------------------------
// the requested location is either ESP or Master
// the maximum number of clusters is 40 in the executor. Each
// cluster maintains a hash table; minimum is 1.
//---------------------------------------------------------
// grouped Table Size is already in Kilobytes
CostScalar groupedTableSize = tableSize;
double maxTableSizeForNumberOfClusters = ActiveSchemaDB()->getDefaults().
getAsDouble(HGB_MAX_TABLE_SIZE_FOR_CLUSTERS);
if (groupedTableSize.value() > maxTableSizeForNumberOfClusters)
{
groupedTableSize=maxTableSizeForNumberOfClusters;
}
else if (groupedTableSize < 100)
{
groupedTableSize=100;
}
double exeMemoryLimit=ActiveSchemaDB()->getDefaults().
getAsDouble(HGB_MEMORY_AVAILABLE_FOR_CLUSTERS) *
csOneKiloBytes.getFloor().value();
clusters = (groupedTableSize / exeMemoryLimit);
//---------------------------------------------
// We must retain one buffer for each cluster.
//---------------------------------------------
maxClusters = (Lng32)((memoryLimit / bufferSize_).getFloor().value());
if(maxClusters != 0 &&
clusters > double(maxClusters))
{
return maxClusters;
}
}
return Lng32(clusters.getCeiling().value());
} // CostMethodHashGroupBy::computeCountOfClusters().
//<pb>
//==============================================================================
// Compute operator cost for a specified Hash GroupBy operator.
//
// Input:
// op -- pointer to specified Hash GroupBy operator.
//
// myContext -- pointer to optimization context for this Hash GroupBy
// operator.
//
// Output:
// countOfStreams -- degree of parallelism for this Hash GroupBy operator.
//
// Return:
// Pointer to computed cost object for this Hash GroupBy operator.
//
//==============================================================================
Cost*
CostMethodHashGroupBy::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
//-----------------------------
// CostScalars to be computed.
//-----------------------------
CostScalar cpuLR(csZero), ioLR(csZero), idleLR(csZero);
CostScalar cpuFR(csZero), ioFR(csZero);
CostScalar cpuBK(csZero), ioBK(csZero);
//j CostScalar mem(csZero), disk(csZero);
//fudge factor for CPUTIME
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
//-------------------
// Preparatory work.
//-------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
//-------------------------------------------
// Save off estimated degree of parallelism.
//-------------------------------------------
countOfStreams = countOfStreams_;
//----------------------------------------------------------------------
// Added on 7/16/97: If we're on our way down the tree and this group
// by is being considered for execution in DP2, generate a zero cost
// object first and come back to cost it later when we're on our way up.
// Set the count of streams to an invalid value (-1) to force us to
// recost on the way back up.
//----------------------------------------------------------------------
if(rpp_->executeInDP2() AND
(NOT context_->getPlan()->getPhysicalProperty()))
{
countOfStreams = -1;
return generateZeroCostObject();
}
deriveParameters();
//===============
// Real work. ==
//===============
GroupByAgg * groupByNode = (GroupByAgg *) op;
PhysicalProperty * sppForMe = (PhysicalProperty *) myContext->
getPlan()->getPhysicalProperty();
NABoolean executeInEsp = FALSE;
if ((sppForMe != NULL) && sppForMe->executeInESPOnly())
executeInEsp = TRUE;
//--------------------------------------------------------------------
// Percentage of groups that fit in memory. Assume all do initially.
//--------------------------------------------------------------------
CostScalar groupingFactor = csOne;
//--------------------------------------------------------------------
// A hash groupby performed in DP2 and partial leaf groupby performed
// in Esp don't do overflow handling. Once the memory allocated
// to it has been used up, rows coming up which
// belong to new groups are just returned to parent grby op as they are.
//--------------------------------------------------------------------
if(rpp_->executeInDP2() ||
(groupByNode->isAPartialGroupByLeaf() &&
executeInEsp))
{
computePartialGroupByLeafCost(cpuFR,cpuLR,cpuBK,groupingFactor);
}
else
{
//----------------------------------------------------------------------
// Compute the hash value of each row. Since we can't return an output
// row until all input rows are hashed, this cost goes into blocking.
//----------------------------------------------------------------------
// -------------------------------------------------------------------
// Distinct is considered non-blocking; add the cost to cpuLR
// -------------------------------------------------------------------
NABoolean considerBlockingCost;
if ((CmpCommon::getDefault(COMP_BOOL_52) == DF_OFF) &&
(gb_->aggregateExpr().isEmpty())
)
considerBlockingCost = FALSE;
else
considerBlockingCost = TRUE;
CostScalar hashCost = cpuCostHashRow_
* rowCountPerStream_ / noOfProbesPerStream_
* ff_cpu;
if (considerBlockingCost)
cpuBK += hashCost;
else
cpuLR += hashCost;
SimpleCostVector cvPassPrev, cvPassCurr;
NABoolean isFirstPass = TRUE;
NABoolean isFRproduced = FALSE;
//--------------------------------------------------------------------
// This loop captures the recursive spilling of clusters. A cluster
// is chosen to spill to disk when the whole input table doesn't fit
// into memory. Once spilling has occurred, the cluster is not fully
// grouped. Its disk image contains only partial groups. Therefore,
// we need to perform a new pass of grouping on the cluster again. In
// that second pass, the image is treated as an input file, and we
// allocate a number of clusters to handle it, just as we did before.
// Again, some clusters in the second round may also spill, leading
// to processing in a third round and so on. This continues until we
// have no more spilling.
//--------------------------------------------------------------------
NABoolean isSpilled;
do
{
//-------------------------------------------------------------------
// Find resource usage for current pass and determine whether or not
// this pass produced an output row.
//-------------------------------------------------------------------
NABoolean isRowProducedInThisPass;
isSpilled = computePassCost(isFirstPass,
cvPassCurr,
isRowProducedInThisPass);
//------------------------------------------------------------------
// All first pass resource usage goes into blocking. Subsequent
// passes go into blocking until an output row is actually produced.
//
//------------------------------------------------------------------
if ( (isFirstPass OR (NOT isFRproduced)) AND considerBlockingCost
)
{
//--------------------------------------------------------------------
// All resource usage goes into blocking until first row is produced.
//--------------------------------------------------------------------
cpuBK += cvPassCurr.getCPUTime();
ioBK += cvPassCurr.getIOTime();
}
else
{
//------------------------------------------------------------------
// After first row is produced, subsequent resource usage goes into
// last row.
//
// Note that we must convert last row cost to a cost for all probes.
//------------------------------------------------------------------
cpuLR += cvPassCurr.getCPUTime() * noOfProbesPerStream_;
ioLR += cvPassCurr.getIOTime() * noOfProbesPerStream_;
//------------------------------------------------------------------
// No pass can begin until the previous pass has completed in its
// entirety, so we need to ensure that an appropriate amount of idle
// time is reflected for each transition between passes.
//------------------------------------------------------------------
SimpleCostVector cvBlockingSum = blockingAdd(cvPassPrev,
cvPassCurr,
rpp_);
idleLR += cvBlockingSum.getIdleTime() * noOfProbesPerStream_;
}
//--------------------------------------------------------------------
// If no rows have been produced in a previous pass, see if this pass
// produced the first row.
//--------------------------------------------------------------------
if(NOT isFRproduced)
{
isFRproduced = isRowProducedInThisPass;
}
//-----------------------------------------------
// All subsequent passes are not the first pass.
//-----------------------------------------------
isFirstPass = FALSE;
//------------------------------------------------------------
// Current pass becomes previous pass in next loop iteration.
//------------------------------------------------------------
cvPassPrev = cvPassCurr;
}
while (isSpilled); // Continue until no more spilling.
//-----------------------------------------------------------------
// Costs to evaluate the having predicates and to copy the rows to
// the result buffer. Rather simplistic FR computation.
//-----------------------------------------------------------------
cpuFR += cpuCostEvalHavingPred_ * ff_cpu;
cpuLR += cpuCostEvalHavingPred_ * groupCountPerStream_ * ff_cpu;
cpuFR += cpuCostReturnRow_ * ff_cpu;
cpuLR += cpuCostReturnRow_ * myRowCountPerStream_ * ff_cpu;
}
//-------------------------------------
// Synthesize the simple cost vectors.
//-------------------------------------
SimpleCostVector cvFR (
cpuFR,
ioFR,
ioFR,
csZero,
noOfProbesPerStream_
);
SimpleCostVector cvLR (
cpuLR,
ioLR,
ioLR,
idleLR,
noOfProbesPerStream_
);
SimpleCostVector cvBK (
cpuBK,
ioBK,
ioBK,
csZero,
noOfProbesPerStream_
);
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"HASHGROUPBY::computeOperatorCost()\n");
fprintf(pfp,"child0RowCount=%g,groupCount=%g,myRowCount=%g\n",
child0RowCount_.value(),groupCount_.value(),myRowCount_.value());
cvFR.print(pfp);
cvLR.print(pfp);
cvBK.print(pfp);
}
#endif
//----------------------------------------
// Synthesize and return the cost object.
//----------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
// If this is a partial Group By leaf in an ESP adjust it's cost
if(groupByNode->isAPartialGroupByLeaf() &&
((sppForMe && sppForMe->executeInESPOnly()) ||
(CmpCommon::getDefault(COMP_BOOL_186) == DF_ON)))
{
// don't adjust if Group Columns contain partition columns.
const PartitioningFunction* const myPartFunc =
sppForMe->getPartitioningFunction();
ValueIdSet myPartKey = myPartFunc->getPartitioningKey();
ValueIdSet myGroupingColumns = groupByNode->groupExpr();
NABoolean myGroupingMatchesPartitioning = FALSE;
if (myPartKey.entries() &&
myGroupingColumns.contains(myPartKey))
myGroupingMatchesPartitioning = TRUE;
if (!myGroupingMatchesPartitioning)
{
CostScalar grpByAdjFactor = (ActiveSchemaDB()->getDefaults())\
.getAsDouble(ROBUST_PAR_GRPBY_LEAF_FCTR);
cvLR *= grpByAdjFactor;
cvFR *= grpByAdjFactor;
cvBK *= grpByAdjFactor;
}
}
Cost *costPtr = new STMTHEAP HashGroupByCost( &cvFR,
&cvLR,
&cvBK,
cpuCount,
fragmentsPerCPU,
groupingFactor
);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodHashGroupBy::computeOperatorCostInternal()
//<pb>
//==============================================================================
// Produce a final cumulative cost for an entire subtree rooted at a specified
// physical Hash GroupBy operator.
//
// Input:
// hashGroupbyOp -- specified physical Hash GroupBy operator.
//
// myContext -- context associated with specified Hash GroupBy operator.
//
// pws -- plan work space associated with specified Hash GroupBy
// operator.
//
// planNumber -- used to get appropriate child contexts.
//
// Output:
// none
//
// Return:
// Pointer to cumulative final cost.
//
//==============================================================================
Cost*
CostMethodHashGroupBy::computePlanCost( RelExpr* hashGroupByOp,
const Context* myContext,
const PlanWorkSpace* pws,
Lng32 planNumber)
{
//-----------------------------------------------------------------------
// Get a local copy of the required physical properties for use in later
// roll-up computations.
//-----------------------------------------------------------------------
const ReqdPhysicalProperty* rpp = myContext->getReqdPhysicalProperty();
//------------------------------------------------------------------------
// if this executes in dp2 call other roll up, so operator is not
// considered blocking
//------------------------------------------------------------------------
if (rpp->executeInDP2() &&
(CmpCommon::getDefault(COMP_BOOL_52) == DF_ON) )
{
return CostMethod::computePlanCost(hashGroupByOp,
myContext,
pws,
planNumber);
}
if ((CmpCommon::getDefault(COMP_BOOL_52) == DF_OFF) &&
((HashGroupBy *)hashGroupByOp)->aggregateExpr().isEmpty())
{
return CostMethod::computePlanCost(hashGroupByOp,
myContext,
pws,
planNumber);
}
//------------------------------------------------------------------------
// If the group-by is because of DISTINCT, then it is not a blocking
// operator; for a distinct, no aggregate expressions exist
//------------------------------------------------------------------------
//------------------------------------------------------------------------
// Get parent's operator cost (independent of its children) from the plan
// workspace.
//
// NOTE: We need to cast constness away since getFinalOperatorCost cannot
// be made const.
//------------------------------------------------------------------------
HashGroupByCost* parentCost =
(HashGroupByCost*)
((PlanWorkSpace *)pws)->getFinalOperatorCost(planNumber);
//------------------------------------------------------------------
// Get child's roll-up cost from a child context stored in the plan
// workspace.
//------------------------------------------------------------------
Context* childContext = pws->getChildContext(0,planNumber);
CMPASSERT(childContext && childContext->hasOptimalSolution());
const Cost* childRollUpCost = childContext->getSolution()->getRollUpCost();
Cost* planCost;
//============================================================================
// When a Hash GroupBy operator executes in DP2, it may encounter a situation
// whereby it can no longer fit a new group into its memory buffers. We call
// this an overflow situation. During an overflow situation, the Hash GroupBy
// operator simply returns single rows to its parent, and the resources
// necessary for the child of the Hash GroupBy operator to produce these rows
// overlap with the Hash GroupBy operator's own last row activity.
//
// Thus, when a Hash GroupBy operator executes in DP2, we need to adjust
// the roll-up formulas to take into account the inherant overlap of an
// overflow situation. If the Hash GroupBy operator does not execute in DP2,
// then we can use the traditional blocking unary roll-up formulas.
//============================================================================
//----------------------------------------------------------------------------
// If Hash GroupBy operator does not execute in DP2, use traditional blocking
// unary roll-up formulas.
//----------------------------------------------------------------------------
if (NOT rpp->executeInDP2())
{
planCost = rollUpUnaryBlocking(*parentCost, *childRollUpCost, rpp);
delete parentCost;
return planCost;
}
//=========================================================================
// At this point we know the Hash GroupBy operator executes in DP2, so use
// the traditional blocking unary roll-up formulas modified to take into
// account potential overlap during an overflow situation.
//=========================================================================
//-----------------------
// Create an empty cost.
//-----------------------
planCost = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// Total cost roll-up and first row roll-up are the same as in traditional
// unary blocking.
//-------------------------------------------------------------------------
planCost->totalCost() = parentCost->getTotalCost()
+ childRollUpCost->getTotalCost();
planCost->cpfr() = parentCost->getCpfr();
//----------------------------------------------------------------------
// A percentage of the child's last row activity overlaps with the Hash
// GroupBy operator's last row activity. The term (1 - groupingFactor)
// represents this percentage.
//----------------------------------------------------------------------
const CostScalar & groupingFactor = parentCost->getGroupingFactor();
planCost->cplr() =
overlapAddUnary(parentCost->getCplr(),
childRollUpCost->getCplr() * (csOne - groupingFactor));
//---------------------------------------------------------------------
// Compute number of probes associated with parent's preliminary cost.
//---------------------------------------------------------------------
const CostScalar & parentNumProbes = parentCost->getCpbc1().getNumProbes();
//-------------------------------------------------
// See if Hash GroupBy is first blocking operator.
//-------------------------------------------------
if ( childRollUpCost->getCpbc1().isZeroVectorWithProbes() )
{
//----------------------------------------------------------------------
// Hash GroupBy is first blocking operator. Use same formula as in
// traditional unary blocking roll-up except that only a percentage of
// the child's last row activity is accumulated into blocking activity.
// The term (groupingFactor) represents this percentage.
//----------------------------------------------------------------------
planCost->cpbc1() =
overlapAddUnary(parentCost->getCpbc1(),
childRollUpCost->getCplr()*groupingFactor/parentNumProbes);
}
else
{
//-------------------------------------------------------------
// Parent not first blocking operator. Roll up first blocking
// cost just as in traditional unary blocking roll-up.
//-------------------------------------------------------------
planCost->cpbc1() =
childRollUpCost->getCpbc1().getNormalizedVersion(parentNumProbes);
}
//-------------------------------------------------------------------------
// The total blocking formula is the same as in traditional unary blocking
// roll-up except that only a percentage of the child's last row activity
// is accumulated into blocking activity. The term (groupingFactor)
// represents this percentage.
//-------------------------------------------------------------------------
planCost->cpbcTotal() =
blockingAdd(
overlapAddUnary(parentCost->getCpbc1(),
childRollUpCost->getCplr() * groupingFactor / parentNumProbes),
childRollUpCost->getCpbcTotal().getNormalizedVersion(parentNumProbes),
rpp);
//------------------------------------------------------------------------
// Overlapped process costs are the same as in traditional unary blocking
// roll-up.
//------------------------------------------------------------------------
//jo planCost->opfr() = childRollUpCost->getOpfr();
//jo planCost->oplr() = childRollUpCost->getOplr();
delete parentCost;
return planCost;
} // CostMethodHashGroupBy::computePlanCost()
//<pb>
// ----QUICKSEARCH FOR ShortCutGroupBy.....................................
/**********************************************************************/
/* */
/* CostMethodShortCutGroupBy */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodShortCutGroupBy::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodShortCutGroupBy::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// CostScalars to be computed.
// ---------------------------------------------------------------------
CostScalar cpu(csZero);
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// Added on 7/16/97: If we're on our way down the tree and this group
// by is being considered for execution in DP2, generate a zero cost
// object first and come back to cost it later when we're on our way up.
// Set the count of streams to an invalid value (0) to force us to
// recost on the way back up.
// ---------------------------------------------------------------------
if(rpp_->executeInDP2() AND
(NOT context_->getPlan()->getPhysicalProperty()))
{
countOfStreams = 0;
return generateZeroCostObject();
}
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// ---------------------------------------------------------------------
// It's hard to cost an ShortCutGroupBy accurately since its execution
// can usually be short-circuited after say, we found one row to have
// satisfied the any-true aggregate expression. This short circuit can
// even lead to the cancellation of the execution of the operator's
// child. Since we cost the full execution of the operator's child, we
// also cost the full execution of the ShortCutGroupBy assuming there
// isn't a short circuit.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// aggrVis should contain only one expr which is rooted by ANYTRUE op.
// ---------------------------------------------------------------------
const ValueIdSet& aggrVis = gb_->aggregateExpr();
CMPASSERT(NOT aggrVis.isEmpty());
ValueId ExprVid = aggrVis.init();
// coverity[check_return]
aggrVis.next(ExprVid);
const ItemExpr * itemExpr = ExprVid.getItemExpr();
OperatorTypeEnum optype = itemExpr->getOperatorType();
CMPASSERT(optype==ITM_MIN OR
optype==ITM_MAX OR
optype==ITM_ANY_TRUE OR
optype==ITM_ANY_TRUE_MAX);
if((optype==ITM_ANY_TRUE) OR (optype==ITM_ANY_TRUE_MAX))
{
const ValueId & anyTruePredVid = itemExpr->child(0).getValueId();
ValueIdSet anyTruePredVis;
anyTruePredVis.insert(anyTruePredVid);
CostScalar cpuCostEvalAnyTruePred =
CostPrimitives::cpuCostForEvalPred(anyTruePredVis);
cpu = (cpuCostPassRow_ + cpuCostEvalAnyTruePred) * child0RowCount_;
// ---------------------------------------------------------------------
// Synthesize the cost vectors.
// ---------------------------------------------------------------------
SimpleCostVector cv (
cpu/countOfStreams_ * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"ShortCutGroupBy::computeOperatorCost()\n");
fprintf(pfp,"childRowCount=%g",child0RowCount_.value());
cv.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return the cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP Cost(&cv,
&cv,
NULL,
cpuCount,
fragmentsPerCPU
);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
}
else // MIN/MAX optimization
{
//tentative costing code
CMPASSERT(op->getFirstNRows()==1);
cpu = cpuCostPassRow_;//it only passes along a single row
SimpleCostVector cv (cpu/countOfStreams_ * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_);
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP Cost (&cv,&cv,NULL,cpuCount,fragmentsPerCPU);
return costPtr;
}
} // ShortCutGroupBy::computeOperatorCostInternal().
Cost*
CostMethodShortCutGroupBy::computePlanCost( RelExpr* op,
const Context* myContext,
const PlanWorkSpace* pws,
Lng32 planNumber
)
{
if (CmpCommon::getDefault(COSTING_SHORTCUT_GROUPBY_FIX) == DF_ON &&
isUnderNestedJoin(op, myContext) == FALSE)
return CostMethod::computePlanCost(op, myContext, pws, planNumber);
//--------------------------------------------------------------------------
// Grab parent's cost (independent of its children) directly from the plan
// work space. This cost should contain result of computeOperatorCost().
//--------------------------------------------------------------------------
// Need to cast constness away since getFinalOperatorCost cannot
// be made const
Cost* parentCost = ((PlanWorkSpace *)pws)->getFinalOperatorCost( planNumber );
//------------------------------------------------------
// Get current child's context via our plan work space.
//------------------------------------------------------
Context* childContext = pws->getChildContext( 0, planNumber );
//------------------------------------------------------------------
// Make sure plans are already generated by the operator's children.
//------------------------------------------------------------------
if ( childContext == NULL )
{
ABORT("CostMethod::computePlanCost(): A child has a NULL context");
}
// Coverity flags this dereferencing null pointer childContext.
// This is a false positive, we fix it using annotation.
// coverity[var_deref_model]
if ( NOT childContext->hasOptimalSolution() )
{
ABORT("CostMethod::computePlanCost(): A child has no solution");
}
//---------------------------------------------
// Accumulate this child's cost into PlanCost.
//---------------------------------------------
Cost mergedChildCost(
*childContext->getSolution()->getRollUpCost() );
/*
if(op->getFirstNRows() == 1)
{
mergedChildCost.cpfr() = mergedChildCost.getCpfr()*0.8 ;
mergedChildCost.cplr() = mergedChildCost.getCpfr() ;
}
*/
Cost* planCost = rollUp( parentCost
, &mergedChildCost
, myContext->getReqdPhysicalProperty()
);
delete parentCost;
return planCost;
} // CostMethodShortCutGroupBy::computePlanCost()
//<pb>
// ----QUICKSEARCH FOR JOIN...............................................
/**********************************************************************/
/* */
/* CostMethodJoin */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodJoin::cacheParameters().
// -----------------------------------------------------------------------
void CostMethodJoin::cacheParameters(RelExpr* op, const Context* myContext)
{
CostMethod::cacheParameters(op,myContext);
jn_ = (Join *) op;
// if inputForSemiTSJ is set for inputLogProp_ it cannot be passed below the join
// so create a new EstLogProp with the flag set off to pass to my children
EstLogPropSharedPtr copyInputEstProp;
if (inLogProp_->getInputForSemiTSJ() != EstLogProp::NOT_SEMI_TSJ)
{
copyInputEstProp = EstLogPropSharedPtr(new (HISTHEAP)
EstLogProp(*inLogProp_));
copyInputEstProp->setInputForSemiTSJ(EstLogProp::NOT_SEMI_TSJ);
}
else
{
copyInputEstProp = inLogProp_;
}
child0LogProp_ = jn_->child(0).outputLogProp(copyInputEstProp);
CMPASSERT(child0LogProp_ != NULL);
child0RowCount_ = ( child0LogProp_->getResultCardinality() ).minCsOne();
// ---------------------------------------------------------------------
// For a TSJ, the input est log prop for the right child is the output
// est log prop of its left child.
// EXCEPT for the case where the TSJ is a semi-Join, in which case that
// extra piece of information has to be added to the input est log prop
// given to the right child.
// ---------------------------------------------------------------------
if ( jn_->isTSJ() )
{
if ( jn_->isSemiJoin() ||
inLogProp_->getInputForSemiTSJ() == EstLogProp::SEMI_TSJ )
{
EstLogPropSharedPtr copyChild0LogProp(new STMTHEAP
EstLogProp(*child0LogProp_));
copyChild0LogProp->setInputForSemiTSJ( EstLogProp::SEMI_TSJ );
child1LogProp_ = jn_->child(1).outputLogProp( copyChild0LogProp );
}
else if ( jn_->isAntiSemiJoin() ||
inLogProp_->getInputForSemiTSJ() == EstLogProp::ANTI_SEMI_TSJ )
{
EstLogPropSharedPtr copyChild0LogProp(new STMTHEAP
EstLogProp(*child0LogProp_));
copyChild0LogProp->setInputForSemiTSJ( EstLogProp::ANTI_SEMI_TSJ );
child1LogProp_ = jn_->child(1).outputLogProp( copyChild0LogProp );
}
else
{
child1LogProp_ = jn_->child(1).outputLogProp( child0LogProp_ );
}
}
else // if semiTSJ set use copy with it set off
{
child1LogProp_ = jn_->child(1).outputLogProp( copyInputEstProp );
}
CMPASSERT(child1LogProp_ != NULL);
child1RowCount_ = ( child1LogProp_->getResultCardinality() ).minCsOne();
//---------------------------------------------------------------------------
// The reuse issue is accounted for by setting
// number of probes to ONE in the final rollup cost in the method
// CostMethodHashJoin::computePlanCost. The rationale for the whole
// Reuse costing is also explained there.
//
// In case of Hash Join Reuse, get outputlogprop from
// materializeOutputLogProp. -OA Mar02
//---------------------------------------------------------------------------
if ( jn_->isHashJoin() AND myContext->getInputLogProp()
->getResultCardinality().isGreaterThanOne() /* > 1 */ )
{
HashJoin * hj = (HashJoin *)jn_;
if(hj->isNoOverflow() AND hj->isReuse())
{
Int32 multipleCalls;
// The right(inner) child's output log prop are set equal to materializeoutputLP
// This essentially means that the number of parent probes for the right
// child is set to ONE IF the right child needs to be materialized only once.
// Right child's output cardinality ==(num parent probes)*(actual number
// of tuples returned by the child). When numParentProbes==1, it just returns
// the actual number of tuples.
child1LogProp_ = jn_->child(1).getGroupAttr()
->materializeOutputLogProp(copyInputEstProp, &multipleCalls);
child1RowCount_ = ( child1LogProp_->getResultCardinality() ).minCsOne();
// It is not clear why changing the number of probes for the join itself
// even if we materialize the right child to have only one probe.
if ( CmpCommon::getDefault(COMP_BOOL_37) == DF_ON AND multipleCalls == 0 )
{
// setting numberOfprobes_ = one, implies, that we are computing this
// for empty input logical properties
noOfProbes_ = csOne;
EstLogPropSharedPtr emptyLogProp = (*GLOBAL_EMPTY_INPUT_LOGPROP);
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
noOfProbesPerStream_ = emptyLogProp->getCardOfBusiestStream(partFunc_,
countOfStreams_,
NULL,
countOfAvailableCPUs_ );
}
else
noOfProbesPerStream_ = ( noOfProbes_ / countOfStreams_ ).minCsOne();
}
}
}
// Make sure clean up is done in the last costing session.
if(isColStatsMeaningful_)
{
ABORT("CostMethodJoin: Didn't clean up ColStats after last session.");
}
// Whether a HJ or MJ has equi-join predicates.
hasEquiJoinPred_ =
(NOT jn_->isTSJ()) AND (NOT jn_->getEquiJoinPredicates().isEmpty());
// ---------------------------------------------------------------------
// If I am not a NJ, estimate result statistics of doing inner join on
// equi-join cols. Such statistics information are useful for MJ and HJ
// costing.
// ---------------------------------------------------------------------
if(NOT jn_->isTSJ()) estimateEquiJoinStats();
// ---------------------------------------------------------------------
// Try to retrieve the ColStats information on the equi join columns
// from the children's EstLogProp and prepare histograms for further
// processing if we can. If operation is successful, results are stored
// in {child0,child1,merged}EquiJoinColStats_.
//
// if(hasEquiJoinPred_)
// isColStatsMeaningful_ = mergeColStatsOnEquiJoinPred();
// else
// isColStatsMeaningful_ = FALSE;
// ---------------------------------------------------------------------
// Store the partitioning functions of the children, if physical
// properties are available.
const PhysicalProperty* sppOfChild0 =
myContext->getPhysicalPropertyOfSolutionForChild(0);
if (sppOfChild0 != NULL)
child0PartFunc_ = sppOfChild0->getPartitioningFunction();
else
child0PartFunc_ = NULL;
const PhysicalProperty* sppOfChild1 =
myContext->getPhysicalPropertyOfSolutionForChild(1);
if (sppOfChild1 != NULL)
child1PartFunc_ = sppOfChild1->getPartitioningFunction();
else
child1PartFunc_ = NULL;
}
//<pb>
// -----------------------------------------------------------------------
// CostMethodJoin::estimateEquiJoinStats()
//
// This method prepares column statistics information on the equi-join
// cols of the children and estimate the row count and uec in the result
// of doing an inner-join on those cols.
//
// Parameters computed by this method include:
// child0EquiJoinColUec_, child1EquiJoinColUec_, resultEquiJoinColUec_,
// and resultEquiJoinRowCount_.
// -----------------------------------------------------------------------
void CostMethodJoin::estimateEquiJoinStats()
{
// ---------------------------------------------------------------------
// $$$ In cases where noOfProbes_!=1, there is a pending problem on the
// $$$ accuracy of the estimates of child0RowCount_ and child1RowCount_
// $$$ and the way the result of the join is computed by Join::synthEst
// $$$ LogProp().
// ---------------------------------------------------------------------
// This method is for MJ and HJ only.
CMPASSERT(NOT jn_->isTSJ());
// The cross product case.
if(NOT hasEquiJoinPred_)
{
child0EquiJoinColUec_ = csOne;
child1EquiJoinColUec_ = csOne;
resultEquiJoinColUec_ = csOne;
// $$$ Read comments at beginning of this method.
CostScalar prodChild0Child1;
//if (child1RowCount_.getValue() < 1.000001)
// child1RowCount_ = CostScalar(1.0);
child1RowCount_.minCsOne();
prodChild0Child1 = child0RowCount_ * child1RowCount_;
resultEquiJoinRowCount_ = (prodChild0Child1 / noOfProbes_ ).minCsOne();
// MIN_ONE_CS(prodChild0Child1 / noOfProbes_ );
return;
}
// ---------------------------------------------------------------------
// First, estimate child0EquiJoinColUec_ and child1EquiJoinUec_.
// ---------------------------------------------------------------------
ValueIdSet equiJoinPreds;
if ( jn_->isMergeJoin() AND
( NOT CURRSTMT_OPTDEFAULTS->optimizerPruning() OR
CmpCommon::getDefault(OPH_USE_ORDERED_MJ_PRED) == DF_ON )
)
{
MergeJoin * mj = (MergeJoin *)jn_;
equiJoinPreds = mj->getOrderedMJPreds();
}
else
equiJoinPreds = jn_->getEquiJoinPredicates();
// The children's list of column statistics.
ColStatDescList& child0ColStatDescList = child0LogProp_->colStats();
ColStatDescList& child1ColStatDescList = child1LogProp_->colStats();
// No of equijoin predicates which are not VEGPreds. Ex (x.a + 1 = y.b).
Lng32 noOfNonVEGPreds = 0;
// ---------------------------------------------------------------------
// Products of the uec's from all the single column statistics of those
// columns present in the VEG predicates.
// ---------------------------------------------------------------------
CostScalar child0UecProduct = csOne;
CostScalar child1UecProduct = csOne;
// Are we missing statistics for some of our VEG predicates ?
CollIndex noOfVEGPredsWithMissingStats = 0;
ValueIdSet equiJoinVEGPreds;
for(ValueId pred = equiJoinPreds.init();
equiJoinPreds.next(pred);
equiJoinPreds.advance(pred))
{
const ItemExpr* predItemExpr = pred.getItemExpr();
CMPASSERT(predItemExpr->isAPredicate());
if(predItemExpr->getOperatorType() != ITM_VEG_PREDICATE)
{
noOfNonVEGPreds++;
}
else
{
equiJoinVEGPreds.insert(pred);
// -----------------------------------------------------------------
// Locate relevant statistics from child0/child1 for the VEG pred.
// -----------------------------------------------------------------
ColStatsSharedPtr child0ColStats =
child0ColStatDescList.getColStatsPtrForPredicate(pred);
ColStatsSharedPtr child1ColStats =
child1ColStatDescList.getColStatsPtrForPredicate(pred);
// -----------------------------------------------------------------
// Retrieve uec information from the ColStats.
// $$$ They shouldn't be NULL after prototype code get discarded.
// -----------------------------------------------------------------
if(child0ColStats != NULL AND child1ColStats != NULL)
{
const CostScalar & child0Uec = child0ColStats->getTotalUec();
const CostScalar & child1Uec = child1ColStats->getTotalUec();
child0UecProduct *= child0Uec;
child1UecProduct *= child1Uec;
}
else noOfVEGPredsWithMissingStats++;
}
}
// ---------------------------------------------------------------------
// Use product of uec's of all join columns (up to a limit of the given
// row count) to approximate uec of multiple join columns, if only one
// of them is missing.
//
// $$$ In the future, we might want to try finding a multiple column
// $$$ statistics describing all the join columns in the VEG predicates
// $$$ instead, but multiple column histograms are not yet considered
// $$$ in phase 1....
//
// ---------------------------------------------------------------------
if ( (equiJoinVEGPreds.entries() > noOfVEGPredsWithMissingStats) &&
(noOfVEGPredsWithMissingStats < 2))
{
child0EquiJoinColUec_ = MINOF(child0UecProduct,child0RowCount_);
child1EquiJoinColUec_ = MINOF(child1UecProduct,child1RowCount_);
}
else // Too many VEG stats are missing.
{
child0EquiJoinColUec_ = child0RowCount_;
child1EquiJoinColUec_ = child1RowCount_;
}
child0EquiJoinColUec_ = ( child0EquiJoinColUec_ ).minCsOne();
child1EquiJoinColUec_ = ( child1EquiJoinColUec_ ).minCsOne();
// ---------------------------------------------------------------------
// Check if the equi-join predicates are just all predicates we have
// and I am an inner-join. In such case, my output statistics are just
// the result equi-join stats.
// ---------------------------------------------------------------------
if(jn_->isInnerNonSemiJoin())
{
// First three are just place holders. We need the forth one.
ValueIdSet vs1;
ValueIdSet vs2;
ValueIdSet vs3;
ValueIdSet otherPreds;
classifyPredicates(vs1,vs2,vs3,otherPreds);
// Case: 10-030611-2757 - BEGIN
// If there are no other predicates evaluated at this join,
// use the myRowCount_ as resultEquiJoinRowCount_ and return.
if(otherPreds.isEmpty())
{
resultEquiJoinRowCount_ = myRowCount_;
return;
}
// Case: 10-030611-2757 - END
}
// ---------------------------------------------------------------------
// Build an inner Join node, include only equi-join predicates and call
// Join::estimateCardinality() to synthesize result statistics.
// ---------------------------------------------------------------------
// Constructor join node and point it to same children as jn_.
OperatorTypeEnum joinType = (jn_->isSemiJoin() || jn_->isAntiSemiJoin()? REL_SEMIJOIN: REL_JOIN);
Join join(NULL,NULL,joinType,NULL,FALSE,TRUE);
join[0] = (*jn_)[0];
join[1] = (*jn_)[1];
// Set its predicates to contain only the equi-join predicates of jn_.
join.selectionPred() = equiJoinPreds;
// Set up group attributes for join to store result in.
GroupAttributes* ga = new STMTHEAP GroupAttributes;
ga->setCharacteristicInputs(ga_->getCharacteristicInputs());
ga->setCharacteristicOutputs(ga_->getCharacteristicOutputs());
ga->addConstraints(ga_->getConstraints());
join.setGroupAttr(ga);
NABoolean inputCacheable = inLogProp_->isCacheable();
// we don't want these stats to be cached in the ASM
if (inputCacheable)
inLogProp_->setCacheableFlag(FALSE);
// Tell the join to synthesize its estimated logical properties.
join.synthEstLogProp(inLogProp_);
// Store result of equi join.
resultEquiJoinRowCount_ =
( ga->outputLogProp(inLogProp_)->getResultCardinality() ).minCsOne();
if (inputCacheable)
inLogProp_->setCacheableFlag(TRUE);
ga->clearAllEstLogProp();
// cardinality after applying to a subset of predicates should not
// go below the cardinality after applying all predicates
// myRowCount_ is the cardinality after applying all predicates
resultEquiJoinRowCount_ = MAXOF(resultEquiJoinRowCount_, myRowCount_);
} // CostMethodJoin::estimateEquiJoinStats().
//<pb>
// -----------------------------------------------------------------------
// CostMethodJoin::estimateDegreeOfParallelism().
//
// Makes use of the generic implementation. Adds logic for dealing with
// the children's per stream row counts. Assumed cacheParameters() has
// been called.
//
// Parameters computed by this method include:
// child0RowCountPerStream_, child1RowCountPerStream_,
// child0UecPerStream_, child1UecPerStream_,
// equiJnRowCountPerStream_ and equiJnUecPerStream_.
//
// The latter 4 are not computed if the join is a NestedJoin, since they
// are currently not used in the cost estimation of NestedJoin.
// -----------------------------------------------------------------------
void CostMethodJoin::estimateDegreeOfParallelism()
{
GroupAttributes * child0GA = jn_->child(0).getGroupAttr();
GroupAttributes * child1GA = jn_->child(1).getGroupAttr();
CostMethod::estimateDegreeOfParallelism();
NABoolean doNotPenalizeSkew = FALSE;
// Operator is a NestedJoin.
if(jn_->isTSJ())
{
const ReqdPhysicalProperty* rppForMe = context_->getReqdPhysicalProperty();
if (((NestedJoin*)jn_)->isProbeCacheApplicable(rppForMe->getPlanExecutionLocation()))
doNotPenalizeSkew = TRUE;
// The uec's are not currently being used in NJ cost analysis.
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) &&
(doNotPenalizeSkew == FALSE))
{
child0RowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
child0GA,
countOfAvailableCPUs_);
child1RowCountPerStream_ = child1LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
child1GA,
countOfAvailableCPUs_);
}
else
{
child0RowCountPerStream_ = ( child0RowCount_ / countOfStreams_ ).minCsOne();
child1RowCountPerStream_ = ( child1RowCount_ / countOfStreams_ ).minCsOne();
}
}
else
{
// -----------------------------------------------------------------
// For HJ and MJ, we are not going to try a SQL/MP style PLAN 1 if
// there are no equi-join predicates. The only thing we consider is
// a PLAN 2, which replicates the right child.
// -----------------------------------------------------------------
if(NOT hasEquiJoinPred_)
{
// ---------------------------------------------------------------
// If a HJ or MJ has no equi-join predicates, at plan generation,
// a predicate of (1==1) which is always TRUE is generated for the
// join so that a cross product is resulted. That's why the uec's
// on the join column is 1, since its value is same for all rows.
// ---------------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
child0RowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
child0GA,
countOfAvailableCPUs_);
}
else
child0RowCountPerStream_ = ( child0RowCount_ / countOfStreams_ ).minCsOne();
child0UecPerStream_ = csOne;
child1RowCountPerStream_ = child1RowCount_;
child1UecPerStream_ = csOne;
// ---------------------------------------------------------------
// $$$ Same note as in estimateEquiJoinStats() applies.
// ---------------------------------------------------------------
CostScalar prodCh0Ch1;
//if (child1RowCountPerStream_.getValue() < 1.000001)
// child1RowCountPerStream_ = CostScalar(1.0);
child1RowCountPerStream_.minCsOne();
prodCh0Ch1 = child0RowCountPerStream_ * child1RowCountPerStream_;
equiJnRowCountPerStream_ = prodCh0Ch1 / noOfProbes_;
equiJnUecPerStream_ = csOne;
}
else
// -----------------------------------------------------------------
// Otherwise, we try both PLAN 1 and PLAN 2. If we are on our
// way down, then we don't know for sure, since the partitioning
// function of the right child is not available yet and we don't
// have access to the plan number. So in this case we will
// underestimate, i.e. assume plan1. If the partitioning function
// of the right child is available then we are on our way back
// up and we can tell for sure.
// -----------------------------------------------------------------
{
// ---------------------------------------------------------------
// Try to use colstats to estimate row counts for a representative
// stream. If that fails, just assume even distribution.
// ---------------------------------------------------------------
// excluded for coverage because below code is disabled
if(isColStatsMeaningful_)
{
// -------------------------------------------------------------
// $$$ This should never be the code path taken in Phase 1 !!
// -------------------------------------------------------------
CMPASSERT(FALSE);
if(NOT computeRepresentativeStream())
{
// -----------------------------------------------------------
// computeRepresentativeStream() fails, just assume even
// distribution of row counts across all available streams.
// -----------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
child0RowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
child0GA,
countOfAvailableCPUs_);
}
else
child0RowCountPerStream_ = ( child0RowCount_ / countOfStreams_ ).minCsOne();
child0UecPerStream_ =
( child0EquiJoinColStats_->getTotalUec()
/ countOfStreams_ ).minCsOne();
// For replicate broadcast, each stream must process all the
// child1 rows, i.e. this is a Type-2 join. Also assume a
// Type-2 join if we are on the way down the tree (i.e. if
// child1PartFunc is NULL).
if ((child1PartFunc_ == NULL) OR
child1PartFunc_->isAReplicateViaBroadcastPartitioningFunction())
{
child1RowCountPerStream_ = child1RowCount_;
child1UecPerStream_ = child1EquiJoinColStats_->getTotalUec();
}
else
{
// No replication, or the right child partitioning function
// is not available. Assume each stream only processes a portion
// of the child1 rows, i.e. a Type1 join.
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
child1RowCountPerStream_ = child1LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
child1GA,
countOfAvailableCPUs_);
}
else
child1RowCountPerStream_ = ( child1RowCount_ / countOfStreams_ ).minCsOne();
child1UecPerStream_ =
( child1EquiJoinColStats_->getTotalUec()
/ countOfStreams_ ).minCsOne();
}
// -----------------------------------------------------------
// This might give an illogical result, but since compute
// RepresentativeStream() fails, we have no other better ways.
// -----------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
ColStatDescList equiJoinColStatDescList;
equiJoinColStatDescList.insert(mergedEquiJoinColStatDesc_);
equiJnRowCountPerStream_ = equiJoinColStatDescList.getCardOfBusiestStream(partFunc_,
countOfStreams_,
child0GA,
countOfAvailableCPUs_);
}
else
equiJnRowCountPerStream_ =
(mergedEquiJoinColStats_->getRowcount() / countOfStreams_).minCsOne();
equiJnUecPerStream_ =
mergedEquiJoinColStats_->getTotalUec() / countOfStreams_;
}
else
{
// -----------------------------------------------------------
// The stream row counts and uec's are already set by
// computeRepresentativeStream. Nothing needs to be done.
// -----------------------------------------------------------
}
}
else
// ---------------------------------------------------------------
// $$$ This is always the code path taken right now, since the
// $$$ code for merging histogram stats is being reconsidered. As
// $$$ a result, isColStatsMeaningful_ is always FALSE.
// ---------------------------------------------------------------
{
// -------------------------------------------------------------
// Use the values obtained from estimateEquiJoinStats().
// -------------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
child0RowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
child0GA,
countOfAvailableCPUs_);
}
else
child0RowCountPerStream_ = ( child0RowCount_ / countOfStreams_ ).minCsOne();
child0UecPerStream_ =
( child0EquiJoinColUec_ / countOfStreams_ ).minCsOne();
// For replicate broadcast, each stream must process all the
// child1 rows, i.e. this is a Type-2 join.
// If we are on the way down the tree (child1PartFunc is NULL) and
// if planNumber is 0, then it's a Type-2 join.
Lng32 planNumber = -1;
PlanWorkSpace* myPws = context_->getPlanWorkSpace();
if (myPws != NULL && (myPws->getCountOfChildContexts() <= 2))
planNumber = PLAN0;
NABoolean type2Plan;
if (ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_95) == 0)
{
if ( jn_->isHashJoin() AND
planNumber == PLAN0 AND
(child1PartFunc_ == NULL OR
child1PartFunc_->isAReplicateViaBroadcastPartitioningFunction())
)
type2Plan = TRUE;
else
type2Plan = FALSE;
}
else
{
if ((child1PartFunc_ == NULL) OR
child1PartFunc_->isAReplicateViaBroadcastPartitioningFunction())
type2Plan = TRUE;
else
type2Plan = FALSE;
}
if (type2Plan)
{
child1RowCountPerStream_ = child1RowCount_;
child1UecPerStream_ = child1EquiJoinColUec_;
}
else
{
// No replication, or the right child partitioning function
// is not available. Assume each stream only processes a portion
// of the child1 rows, i.e. a Type1 join.
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
child1RowCountPerStream_ = child1LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
child1GA,
countOfAvailableCPUs_);
}
else
child1RowCountPerStream_ = ( child1RowCount_ / countOfStreams_ ).minCsOne();
child1UecPerStream_ =
( child1EquiJoinColUec_ / countOfStreams_ ).minCsOne();
}
// -------------------------------------------------------------
// $$$ This, together with the above statistics, may give an
// $$$ illogical picture. A good solution to deal with this is
// $$$ yet to be devised.
// -------------------------------------------------------------
equiJnRowCountPerStream_ =
(resultEquiJoinRowCount_ / countOfStreams_);
} // endif(NOT computeRepresentativeStream())
} // endif(NOT hasEquiJoinPred_)
} // endif(jn_->isTSJ())
} // CostMethodJoin::estimateDegreeOfParallelism().
//<pb>
// -----------------------------------------------------------------------
// CostMethodJoin::computeRepresentativeStream().
//
// This method tries to use more sophisticated techniques to come up with
// what is called "a representative stream" for costing. Essentially it
// is an imagined stream with an effective cost which best represents the
// actual stream cost of the operation in the cases where there maybe an
// inherent uneven distribution of workload across different streams.
// -----------------------------------------------------------------------
NABoolean CostMethodJoin::computeRepresentativeStream()
{
// This method needs more refinement and thoughts...
return FALSE;
#if 0
GroupAttributes * child0GA = jn_->child(0).getGroupAttr();
GroupAttributes * child1GA = jn_->child(1).getGroupAttr();
// This is essentially an implementation shared by MJ and HJ but not NJ.
if(jn_->isTSJ()) return FALSE;
// Cannot do better if no colstats are available for analysis.
if(NOT isColStatsMeaningful_) return FALSE;
// Simple case.
if(countOfStreams_ == 1)
{
child0RowCountPerStream_ = child0RowCount_;
child0UecPerStream_ = child0EquiJoinColStats_->getTotalUec();
child1RowCountPerStream_ = child1RowCount_;
child1UecPerStream_ = child1EquiJoinColStats_->getTotalUec();
/*
equiJnRowCountPerStream_ = mergedEquiJoinColStats_->getRowcount();
equiJnUecPerStream_ = mergedEquiJoinColStats_->getTotalUec();
*/
return TRUE;
}
// ---------------------------------------------------------------------
// Check if the no of uec's present limits our degree of parallelism.
// If yes, we could use at most a number of streams equal to the total
// no of uec's could be active. In such a case, we consider assigning
// one uec to each stream, and cost the stream with the largest row
// count per uec.
//
// More analysis could be done in Phase 2 to pick a better stream...
// For example,
//
// 1. Compute average row count per uec from the merged statistics.
// 2. Compute average row connt per uec for each merged histogram int.
// 3. If (2>1) there exists one unique value with many rows. For the
// join to be done correctly, all of such rows must come from the
// same stream. Now, the cost of such a stream is much higher than
// the rest due to data skew. For LR cost elapsed time optimization,
// we should pick such a stream to cost. On the other hand, for FR
// cost, we should in fact just pick the average stream.
// 4. Otherwise, we could believe in having a good hash partitioning
// function or partitioning boundaries such that the workload are
// spread evenly across.
// ---------------------------------------------------------------------
// countOfStreams_ is designed to be smaller than INT_MAX.
if (maxDegreeOfParallelism_.value() < double(INT_MAX))
{
if (countOfStreams_ > Lng32(maxDegreeOfParallelism_.value()))
{
// Not all streams could be active.
countOfStreams_ = Lng32(maxDegreeOfParallelism_.value());
CollIndex intIndex;
// Pick biggest merged result set to be our representative stream.
intIndex = mergedEquiJoinColStats_->getHistogram()->
pickIntervalWithLargestRowCountPerUec();
const HistInt& histInt0 =
child0EquiJoinColStats_->getHistogram()->at(intIndex);
child0RowCountPerStream_ =
histInt0.getCardinality() / histInt0.getFudgedUec();
const HistInt& histInt1 =
child1EquiJoinColStats_->getHistogram()->at(intIndex);
child1RowCountPerStream_ =
histInt1.getCardinality() / histInt1.getFudgedUec();
child0UecPerStream_ = csOne;
child1UecPerStream_ = csOne;
const HistInt& histIntM =
mergedEquiJoinColStats_->getHistogram()->at(intIndex);
equiJnRowCountPerStream_ =
histIntM.getCardinality() / histIntM.getFudgedUec();
equiJnUecPerStream_ = csOne;
return TRUE;
}
}
// ---------------------------------------------------------------------
// For now, just come up with some averages in the other case where
// parallelism is not limited by uec.
// ---------------------------------------------------------------------
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
child0RowCountPerStream_ = child0LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
child0GA,
countOfAvailableCPUs_);
}
else
child0RowCountPerStream_ = ( child0RowCount_ / countOfStreams_ ).minCsOne();
child0RowCountPerStream_ = (child0RowCountPerStream_).minCsOne();
child0UecPerStream_ =
child0EquiJoinColStats_->getTotalUec() / countOfStreams_;
child0UecPerStream_ = (child0UecPerStream_).minCsOne();
// For replicate broadcast, each stream must process all the
// child1 rows, i.e. this is a Type-2 join. Also assume a
// Type-2 join if we are on the way down the tree (i.e. if
// child1PartFunc is NULL).
if ((child1PartFunc_ == NULL) OR
child1PartFunc_->isAReplicateViaBroadcastPartitioningFunction())
{
child1RowCountPerStream_ = child1RowCount_;
child1UecPerStream_ = child1EquiJoinColStats_->getTotalUec();
}
else
{
// No replication, or the right child partitioning function
// is not available. Assume each stream only processes a portion
// of the child1 rows, i.e. a Type1 join.
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
child1RowCountPerStream_ = child1LogProp_->getCardOfBusiestStream(partFunc_,
countOfStreams_,
child1GA,
countOfAvailableCPUs_);
}
else
child1RowCountPerStream_ = ( child1RowCount_ / countOfStreams_ ).minCsOne();
child1UecPerStream_ =
child1EquiJoinColStats_->getTotalUec() / countOfStreams_;
child1UecPerStream_ = (child1UecPerStream_).minCsOne();
}
if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) &&
(partFunc_ != NULL) )
{
ColStatDescList equiJoinColStatDescList;
equiJoinColStatDescList.insert(mergedEquiJoinColStatDesc_);
equiJnRowCountPerStream_ = equiJoinColStatDescList.getCardOfBusiestStream(partFunc_,
countOfStreams_,
child0GA,
countOfAvailableCPUs_);
}
else
equiJnRowCountPerStream_ = (mergedEquiJoinColStats_->getRowcount() /
countOfStreams_).minCsOne();
equiJnUecPerStream_ = mergedEquiJoinColStats_->getTotalUec() /
countOfStreams_;
if(equiJnUecPerStream_ > MINOF(child0UecPerStream_,child1UecPerStream_))
equiJnUecPerStream_ = MINOF(child0UecPerStream_,child1UecPerStream_);
CostScalar maxEquiJnRowCountPerStream =
(child0RowCountPerStream_ * child1RowCountPerStream_)/
MAXOF(child0UecPerStream_,child1UecPerStream_);
equiJnRowCountPerStream_ =
MINOF(equiJnRowCountPerStream_,maxEquiJnRowCountPerStream);
return TRUE;
#endif // 0
} // CostMethodJoin::computeRepresentativeStream().
//<pb>
// -----------------------------------------------------------------------
// CostMethodJoin::mergeHistogramsOnEquiJoinPred().
//
// This method merges the column statistics on the left child of the col
// referenced in the equi-join predicate with the column statistics on
// the right child of the col referenced in the same predicate.
// -----------------------------------------------------------------------
NABoolean CostMethodJoin::mergeHistogramsOnEquiJoinPred()
{
const ValueIdSet& equiJoinPred = jn_->getEquiJoinPredicates();
// Don't do multi-column predicates for now.
if(equiJoinPred.isEmpty() OR equiJoinPred.entries() != 1) return FALSE;
const ValueIdList& child0ColList = jn_->getEquiJoinExprFromChild0();
CMPASSERT(child0ColList.entries() == 1);
const ValueIdList& child1ColList = jn_->getEquiJoinExprFromChild1();
CMPASSERT(child1ColList.entries() == 1);
// The objects under the ValueId's are VEGRefs.
const ValueId & child0Col = child0ColList[0];
const ValueId & child1Col = child1ColList[0];
// Fix for coverity cid #1512 : pointers child1ColItemExpr
// child0ColItemExpr are unused.
// const ItemExpr* child0ColItemExpr = child0Col.getItemExpr();
// const ItemExpr* child1ColItemExpr = child1Col.getItemExpr();
const ColStatDescList& child0ColStats = child0LogProp_->colStats();
const ColStatDescList& child1ColStats = child1LogProp_->colStats();
// List of indices of child0ColStats which reference child0Col.
NAList<CollIndex> child0RefColStatsIndices(CmpCommon::statementHeap());
NAList<CollIndex> placeHolder(CmpCommon::statementHeap());
// $$$ Needed to change method to public.
// child0ColStats.identifyMergeCandidates(child0ColItemExpr,
// child0RefColStatsIndices,
// placeHolder);
if(child0RefColStatsIndices.isEmpty()) return FALSE;
// List of indices of child1ColStats which reference child1Col.
NAList<CollIndex> child1RefColStatsIndices(CmpCommon::statementHeap());
// $$$ Needed to change method to public.
// child1ColStats.identifyMergeCandidates(child1ColItemExpr,
// child1RefColStatsIndices,
// placeHolder);
if(child1RefColStatsIndices.isEmpty()) return FALSE;
// Should there be only one histogram from one side which can be merged?
if(child0RefColStatsIndices.entries() != 1 OR
child1RefColStatsIndices.entries() != 1) return FALSE;
// ---------------------------------------------------------------------
// Get the column statistics to merge. If histograms are just faked,
// normal even distribution assumption will do the job.
// ---------------------------------------------------------------------
CollIndex child0RootIndex = child0RefColStatsIndices[0];
ColStatDescSharedPtr child0RootDesc = child0ColStats[child0RootIndex];
CollIndex child1RootIndex = child1RefColStatsIndices[0];
ColStatDescSharedPtr child1RootDesc = child1ColStats[child1RootIndex];
ColStatDescList myColStatDescList(CmpCommon::statementHeap());
myColStatDescList.insertDeepCopyAt(0,child0RootDesc);
myColStatDescList.insertDeepCopyAt(1,child1RootDesc);
myColStatDescList.insertIntoUecList (child0ColStats.getUecList()) ;
myColStatDescList.insertIntoUecList (child1ColStats.getUecList()) ;
// Get the real working copy of column statistics objects from the desc.
ColStatsSharedPtr child0ColStat = myColStatDescList[0]->getColStatsToModify();
if(child0ColStat->isFakeHistogram()) return FALSE;
ColStatsSharedPtr child1ColStat = myColStatDescList[1]->getColStatsToModify();
if(child1ColStat->isFakeHistogram()) return FALSE;
// Store them in cache.
child0EquiJoinColStats_ = child0ColStat;
child1EquiJoinColStats_ = child1ColStat;
// Original histograms of children.
HistogramSharedPtr child0Histogram = child0ColStat->getHistogramToModify();
HistogramSharedPtr child1Histogram = child1ColStat->getHistogramToModify();
// A template will be made out of the histograms in children's ColStats.
HistogramSharedPtr child0Template = child0Histogram->
createMergeTemplate(child1Histogram,TRUE /*equiMerge*/);
// Make a template for child1 as well.
HistogramSharedPtr child1Template(new HISTHEAP Histogram(*child0Template, HISTHEAP));
// Make a template for the merged histogram.
HistogramSharedPtr mergedTemplate(new HISTHEAP Histogram(*child0Template, HISTHEAP));
// Set the template's row counts and uec's.
ColStats child0tmp ( child1Template, STMTHEAP );
ColStats child1tmp ( child0Template, STMTHEAP );
child0tmp.populateTemplate (child0ColStat);
child1tmp.populateTemplate (child1ColStat);
// child0tmp, child1tmp not used after this point
// be careful! populateTemplate may have compressed the intervals if
// the resulting rowcount was too low!
if ( child0Template->entries() != child1Template->entries() OR
child0Template->entries() != mergedTemplate->entries() )
{
child0Template->condenseToSingleInterval() ; // one of these
child1Template->condenseToSingleInterval() ; // is redundant
mergedTemplate->condenseToSingleInterval() ;
}
CMPASSERT ( child0Template->entries() == child1Template->entries() ) ;
CMPASSERT ( child0Template->entries() == mergedTemplate->entries() ) ;
// ---------------------------------------------------------------------
// Replace the histograms in the children's ColStats with the templates
// so that the interval numbers match with the merged histogram. The
// original histograms needn't be deallocated since the original
// children's ColStats are pointing to them.
// ---------------------------------------------------------------------
child0ColStat->setHistogram(child0Template);
child1ColStat->setHistogram(child1Template);
child0ColStat->setRedFactor (csOne);
child1ColStat->setRedFactor (csOne);
child0ColStat->setUecRedFactor(csOne);
child1ColStat->setUecRedFactor(csOne);
CostScalar rowCount, uec, totalRowCount, totalUec;
CostScalar child0Uec, child0RowCount, child1Uec, child1RowCount;
CollIndex i(1);
// To be recomputed.
maxDegreeOfParallelism_ = csZero;
// Perform the merge.
while(i < mergedTemplate->entries())
{
child0RowCount = ((*child0Template)[i].getCardinality());
child0Uec = ((*child0Template)[i].getUec());
child1RowCount = ((*child1Template)[i].getCardinality());
child1Uec = ((*child1Template)[i].getUec());
maxDegreeOfParallelism_ += MAXOF(child0Uec,child1Uec);
uec = MINOF(child0Uec,child1Uec);
if(uec.isGreaterThanZero() /* > csZero */)
rowCount = (child0RowCount * child1RowCount) /
MAXOF(child0Uec,child1Uec);
else
rowCount = csZero;
(*mergedTemplate)[i].setCardAndUec(rowCount, uec);
totalRowCount += rowCount;
totalUec += uec;
i++;
}
maxDegreeOfParallelism_ = (maxDegreeOfParallelism_).minCsOne();
// ---------------------------------------------------------------------
// Synthesize the merged ColsStat. Note that we don't set the min/max
// value of this ColStats since they are not used in later computation.
// ---------------------------------------------------------------------
ComUID id(ColStats::nextFakeHistogramID());
mergedEquiJoinColStats_ = ColStatsSharedPtr(
new HISTHEAP ColStats( id,
totalUec,
totalRowCount,
// added baseRowCount for testing initialized baseRowCount with totalRowCount
// 11/30 RV
totalRowCount,
FALSE,
FALSE,
mergedTemplate,
FALSE,
csOne, // default row reduction factor
csOne, // default uec reduction factor
-1, // default avg VarChar size
HISTHEAP));
ColStatDescSharedPtr equiJoinStatDesc(new (HISTHEAP)
ColStatDesc (mergedEquiJoinColStats_,child0Col), HISTHEAP);
equiJoinStatDesc->VEGColumn() = child0Col;
equiJoinStatDesc->mergeState().clear() ;
equiJoinStatDesc->mergeState().insert(child0Col);
equiJoinStatDesc->mergeState().insert(child1Col);
mergedEquiJoinColStatDesc_ = equiJoinStatDesc;
return TRUE;
}
//<pb>
// -----------------------------------------------------------------------
// CostMethodJoin::classifyPredicates().
//
// The method classifies the predicates this Hash Join or Merge Join is
// evaluating into three types - Equi Join preds, other join preds and
// selection preds. The result is a set of inner equi-join keys and outer
// equi-join keys with the other predicates.
// -----------------------------------------------------------------------
void CostMethodJoin::classifyPredicates(ValueIdSet& innerEquiJoinKeys,
ValueIdSet& outerEquiJoinKeys,
ValueIdSet& otherJoinPreds,
ValueIdSet& otherSelPreds)
{
CMPASSERT(NOT jn_->isTSJ());
// ---------------------------------------------------------------------
// Hash join predicates are equi join predicates which have already been
// chosen by the Hash Join Implementation Rule.
// ---------------------------------------------------------------------
innerEquiJoinKeys.insertList(jn_->getEquiJoinExprFromChild1());
outerEquiJoinKeys.insertList(jn_->getEquiJoinExprFromChild0());
// ---------------------------------------------------------------------
// For left joins, we might have predicates both in joinPred() as well
// as selectionPred(). joinPred() of left joins have to be handled
// specially, since we could not push down even covered predicates like
// VEGPred(VEG{T.x,5}) to the left child T. Those rows not with
// (T.x != 5) have to be null-instantiated by the left join itself
// rather than thrown away by the left child directly.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Consider joinPred() first for left-joins and semi-joins. Inner non-
// semi join has selectionPred() only.
// ---------------------------------------------------------------------
ValueIdSet predSet = jn_->joinPred();
NABoolean isConsideringJoinPred = TRUE;
if(jn_->isInnerNonSemiJoin())
{
// -------------------------------------------------------------------
// This is sometimes not true when the join is originally an outer
// join transformed into an inner join. In that case, the joinPred()
// is not cleared, but nevertheless the predicates are copied to
// selectionPred(), thus, they needn't be considered anymore.
// CMPASSERT(predSet.isEmpty());
// -------------------------------------------------------------------
predSet = jn_->selectionPred();
isConsideringJoinPred = FALSE;
}
// Use break to get out of loop.
while(1)
{
ValueIdSet predsToKeep;
for( ValueId pred = predSet.init();
predSet.next( pred );
predSet.advance( pred ) )
{
const ItemExpr* itemPred = pred.getItemExpr();
// -----------------------------------------------------------------
// Normally we don't keep VEG predicates. VEG without constants in
// them and covered by the both children of this join node have
// already been moved away as equi-join preds. The ones left behind
// are either those uncovered (which are most probably to evaluated
// at a parent join node rather than this one), or with constants in
// them, which are not true join predicates and will eventually get
// pushed down. An exception to this are the joinPred() of a left
// join, at which a VEG predicate with a reference to the output of
// its left child will not get pushed down despite it has constants
// in it. For example, we could not push down VEGPred(VEG{T.x,5})
// to the left child T. Those rows with (T.x != 5) have to be null-
// instantiated by the left join itself rather than get thrown away
// by the left child directly.
// -----------------------------------------------------------------
if(itemPred->getOperatorType() == ITM_VEG_PREDICATE)
{
if(jn_->isLeftJoin() AND isConsideringJoinPred)
{
const VEG* predVEG = ((VEGPredicate *) itemPred)->getVEG();
const ValueIdSet& VEGGroup = predVEG->getAllValues();
ItemExpr* constant = NULL;
if(VEGGroup.referencesAConstValue(&constant))
{
const ValueId & VEGGroupId =
predVEG->getVEGReference()->getValueId();
if(jn_->child(0).getGroupAttr()->
isCharacteristicOutput(VEGGroupId))
{
// -----------------------------------------------------------
// VEG with a constant and a reference to an output from the
// left child. It is to be evaluated at this join node because
// it can not be pushed down to the left. (See comments above)
// -----------------------------------------------------------
predsToKeep.insert(pred);
}
}
}
}
else
{
// -----------------------------------------------------------------
// Other cases are OR predicates, inequality join predicates which
// are to be kept. Inequality non-join predicates are already pushed
// down during normalization and shouldn't appear here.
// -----------------------------------------------------------------
predsToKeep.insert(pred);
}
} // end of for-loop iterating the predicate set.
if(isConsideringJoinPred)
{
otherJoinPreds = predsToKeep;
isConsideringJoinPred = FALSE;
predsToKeep.clear();
predSet = jn_->selectionPred();
if(jn_->isSemiJoin() || jn_->isAntiSemiJoin())
{
CMPASSERT(predSet.isEmpty());
break;
}
}
else
{
otherSelPreds = predsToKeep;
break;
}
} // end of while-loop.
}
//<pb>
// -----------------------------------------------------------------------
// CostMethodJoin::cleanUp()
//
// The method cleans up cached parameters which need deallocation and
// should be called after a costing session is done.
// -----------------------------------------------------------------------
void CostMethodJoin::cleanUp()
{
// Make sure we deallocate ColStats stored during the last invocation.
if(isColStatsMeaningful_)
{
CMPASSERT(child0EquiJoinColStats_ != NULL);
CMPASSERT(child1EquiJoinColStats_ != NULL);
CMPASSERT(mergedEquiJoinColStats_ != NULL);
isColStatsMeaningful_ = FALSE;
child0EquiJoinColStats_ = NULL;
child1EquiJoinColStats_= NULL;
mergedEquiJoinColStats_= NULL;
mergedEquiJoinColStatDesc_ = NULL;
}
// Case: 10-030611-2757 - BEGIN
// Cleaning the variable resultEquiJoinRowCount_ here to take care
// of the problem in reusing it without initializing.
// Similarly all the other variables also need to be cleaned here.
resultEquiJoinRowCount_ = csZero;
// Case: 10-030611-2757 - END
// Reset the EstLogPropSharedPtr objects
child0LogProp_ = 0;
child1LogProp_ = 0;
// Clean up fields in base class
CostMethod::cleanUp();
} // CostMethodJoin::cleanUp().
//<pb>
// ----QUICKSEARCH FOR HJ.................................................
/**********************************************************************/
/* */
/* CostMethodHashJoin */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodHashJoin::cacheParameters().
// -----------------------------------------------------------------------
void CostMethodHashJoin::cacheParameters(RelExpr* op,
const Context* myContext)
{
CostMethodJoin::cacheParameters(op,myContext);
// if child1PartFunc_ is NULL, which means going down the tree,
// get partition function from child context that has been already optimized.
if (child1PartFunc_ == NULL)
{
Lng32 planNumber = 0;
PlanWorkSpace* myPws = myContext->getPlanWorkSpace();
if (myPws != NULL)
planNumber = myPws->getLatestPlan();
if (planNumber > PLAN0)
{
Context* childContext;
if (planNumber == PLAN1)
// get child0 context to access child0 partFunc
childContext = myPws->getChildContext(0, planNumber);
else
childContext = myPws->getChildContext(1, planNumber);
if ( childContext != NULL && childContext->hasOptimalSolution() )
{
const PhysicalProperty* sppOfChild =
childContext->getPhysicalPropertyForSolution();
if (sppOfChild != NULL)
// Though we assign child0 partFunc to child1 partFunc, it's
// really not going to harm in this case, because it's being used
// only for the purpose of preliminary cost estimation
child1PartFunc_ = sppOfChild->getPartitioningFunction();
}
}
}
hj_ = (HashJoin*) op;
// ---------------------------------------------------------------------
// Find out what the predicates are to be evaluated at this HJ node,
// and compute cost primitives related to them.
// ---------------------------------------------------------------------
ValueIdSet innerHashKeys;
ValueIdSet outerHashKeys;
ValueIdSet otherJoinPreds;
ValueIdSet otherSelPreds;
classifyPredicates(
innerHashKeys,outerHashKeys,otherJoinPreds,otherSelPreds);
if(innerHashKeys.isEmpty())
{
cpuCostHashRow_ = csZero;
cpuCostCompareHashKeys_ = csZero;
}
else
{
cpuCostHashRow_ = CostPrimitives::cpuCostForHash(innerHashKeys);
cpuCostCompareHashKeys_ =
CostPrimitives::cpuCostForCompare(innerHashKeys);
}
if(otherJoinPreds.isEmpty())
cpuCostEvalOtherJoinPreds_ = csZero;
else
cpuCostEvalOtherJoinPreds_ = CostPrimitives::
cpuCostForEvalPred(otherJoinPreds);
if(otherSelPreds.isEmpty())
cpuCostEvalOtherSelPreds_ = csZero;
else
cpuCostEvalOtherSelPreds_ = CostPrimitives::
cpuCostForEvalPred(otherSelPreds);
if(hj_->isLeftJoin())
{
if(hj_->nullInstantiatedOutput().isEmpty())
cpuCostNullInst_ = csZero;
else
cpuCostNullInst_ = CostPrimitives::
cpuCostForCopyRow(hj_->nullInstantiatedOutput().getRowLength());
}
// Length of a row from the left table.
GroupAttributes* child0GA = hj_->child(0).getGroupAttr();
child0RowLength_ = child0GA->getRecordLength();
extChild0RowLength_ = child0RowLength_ + hashedRowOverhead_;
// Length of a row from the right table.
GroupAttributes* child1GA = hj_->child(1).getGroupAttr();
child1RowLength_ = child1GA->getRecordLength();
extChild1RowLength_ = child1RowLength_ + hashedRowOverhead_;
// Cost for making a copy of those rows to the local buffer.
cpuCostCopyChild0Row_ = CostPrimitives::
cpuCostForCopyRow(child0RowLength_);
cpuCostCopyChild1Row_ = CostPrimitives::
cpuCostForCopyRow(child1RowLength_);
// Cost for the whole set of outer rows to probe the hash table.
cpuCostTotalProbing_ = computeTotalProbingCost();
// temporary vectors must be NULL:
DCMPASSERT(stage1cvBK_ == NULL);
DCMPASSERT(stage2cvBK_ == NULL);
DCMPASSERT(stage2cvFR_== NULL);
DCMPASSERT(stage2cvLR_ == NULL);
DCMPASSERT(stage3cvFR_ == NULL);
DCMPASSERT(stage3cvLR_ == NULL);
DCMPASSERT(stage3cvBK_ == NULL);
}
//<pb>
// -----------------------------------------------------------------------
// CostMethodHashJoin::deriveParameters().
//
// This method computes derived parameters associated with a HJ's three
// stages of operation. It assumes both cacheParameters() as well as
// estimateDegreeOfParallelism() have been called.
// -----------------------------------------------------------------------
void CostMethodHashJoin::deriveParameters()
{
// ---------------------------------------------------------------------
// The metrics which follow are all on a per-stream-per-probe basis.
// ---------------------------------------------------------------------
CostScalar innerRowCount = csZero;
// Adjustments for OHJ when Reuse is set. This change is required since
// we don't set noOfProbes to One.
if ( (CmpCommon::getDefault(COMP_BOOL_37) == DF_OFF) AND
hj_->isReuse() AND (hj_->multipleCalls() == 0) )
innerRowCount = child1RowCountPerStream_;
else
innerRowCount = (child1RowCountPerStream_ / noOfProbesPerStream_).minCsOne();
CostScalar innerTableSize =
innerRowCount / csOneKiloBytes * extChild1RowLength_;
CostScalar outerRowCount =
(child0RowCountPerStream_ / noOfProbesPerStream_).minCsOne();
CostScalar outerTableSize =
outerRowCount / csOneKiloBytes * extChild0RowLength_;
// ---------------------------------------------------------------------
// Simple case. Quick way out.
// ---------------------------------------------------------------------
if((NOT isBMO_) OR (innerTableSize <= memoryLimit_)
OR (hj_->isNoOverflow()))
{
noOfClusters_ = csOne;
noOfInnerClustersOccupied_ = noOfOuterClustersOccupied_ = csOne;
noOfInnerClustersInMemory_ = csOne;
noOfOuterClustersFlushed_ = csZero;
innerClusterSize_ = innerTableSize;
clusterSizeAfterSplitsOverFlow_ = innerTableSize;
estimatedNumberOfOverflowClusters_ = csZero;
outerClusterSize_ = outerTableSize;
mem_ = innerTableSize;
// -------------------------------------------------------------------
// For more details on this first row analysis, read comments on code
// close to the end of this method.
// -------------------------------------------------------------------
CostScalar outerRowCountForFR = (child0RowCount_ / myRowCount_).minCsOne();
outerRowCountForFR = MINOF(outerRowCountForFR,outerRowCount);
stage2WorkFractionForFR_ = (outerRowCountForFR / outerRowCount);
stage3WorkFractionForFR_ = csZero;
return;
}
else mem_ = memoryLimit_;
// ---------------------------------------------------------------------
// We don't average the uec over the probes. We want to assume, if we
// can, the outer references and the equi-join column are non-corelated.
// However, row count is always a logical upper limit of the uec.
// ---------------------------------------------------------------------
CostScalar innerUec = MINOF(child1UecPerStream_,innerRowCount);
CostScalar outerUec = MINOF(child0UecPerStream_,outerRowCount);
// Just in case.
innerUec = MIN_ONE(innerUec);
outerUec = MIN_ONE(outerUec);
// ---------------------------------------------------------------------
// Decide number of clusters to allocate and also decide number of overflow
// clusters
// ---------------------------------------------------------------------
Lng32 idealNoOfClusters;
if ( CmpCommon::getDefault(COMP_BOOL_54) == DF_ON )
{
// -------------------------------------------------------------------
// Now assume that we have an even distribution of rows among a no of
// clusters. Compute that no of clusters, such that the size of one
// cluster together with one buffer from each remaining clusters could
// just fit into the memory limit. This may be a good estimate for the
// final no of clusters after splitting, on condition that the value
// of uec (of the hash keys) doesn't limit the maximum no of clusters
// the rows could possibly be hashed to.
// -------------------------------------------------------------------
idealNoOfClusters = computeIdealCountOfClusters(
memoryLimit_,innerTableSize);
}
else
{
// -------------------------------------------------------------------
// Calculate number of clusters (at most four) and number of overflow
// clusters
// -------------------------------------------------------------------
idealNoOfClusters = computeInitialCountOfClusters(
memoryLimit_,innerTableSize);
}
if (innerUec.value() < (double) idealNoOfClusters)
{
// Splitting occurs until each uec takes up one cluster.
noOfClusters_ = innerUec.getCeiling();
}
else
{
// Splitting occurs until the ideal no of clusters is reached.
noOfClusters_ = idealNoOfClusters;
}
// if cross product, noOfInnerClustersOccupied_ is one
if (NOT hasEquiJoinPred_)
noOfInnerClustersOccupied_ = noOfClusters_ ;
else
noOfInnerClustersOccupied_ = noOfClusters_ +
estimatedNumberOfOverflowClusters_;
// ---------------------------------------------------------------------
// Now compute the average cluster size across the clusters occupied.
// Also, find out what fraction of the inner table will get flushed to
// disk.
//
// innerClusterSize_ refers to clustersize as if no overflow has taken place
// clusterSizeAfterSplitsOverFlow_ is close to memory limit (that is after
// overflow has occured)
// ---------------------------------------------------------------------
innerClusterSize_ = innerTableSize / noOfClusters_ ;
clusterSizeAfterSplitsOverFlow_ = innerTableSize / (noOfClusters_ +
estimatedNumberOfOverflowClusters_);
if ( CmpCommon::getDefault(COMP_BOOL_54) == DF_ON )
{
noOfInnerClustersInMemory_ = (innerClusterSize_ > memoryLimit_ ? 0 : 1);
}
else
{
noOfInnerClustersInMemory_ = memoryLimit_ / innerClusterSize_.getValue();
noOfInnerClustersInMemory_ = noOfInnerClustersInMemory_.getFloor();
// noOfInnerClustersInMemory_ could be zero
}
//------------------------------------------------------------------------
// adjust number of overflow clusters if number of inner clusters does not
// match number of clusters fit in memory
//------------------------------------------------------------------------
NABoolean adjustClusters = FALSE;
if ( noOfInnerClustersInMemory_ != noOfInnerClustersOccupied_ &&
estimatedNumberOfOverflowClusters_.isZero()
)
{
estimatedNumberOfOverflowClusters_ = noOfInnerClustersOccupied_
- noOfInnerClustersInMemory_;
noOfInnerClustersOccupied_ = noOfInnerClustersInMemory_;
adjustClusters = TRUE;
}
// ---------------------------------------------------------------------
// Now consider the outer table. If it has more uec's than there are
// no of clusters, assume even distribution of its rows across these
// clusters. Otherwise, assume each uec takes up one cluster.
// ---------------------------------------------------------------------
if (NOT hasEquiJoinPred_)
noOfOuterClustersOccupied_ = noOfClusters_;//this is one for a cross product
else if (noOfClusters_ < outerUec.getCeiling() && adjustClusters)
noOfOuterClustersOccupied_ = (noOfInnerClustersOccupied_ +
estimatedNumberOfOverflowClusters_);
else if (noOfClusters_ < outerUec.getCeiling())
noOfOuterClustersOccupied_ = noOfInnerClustersOccupied_;
else
noOfOuterClustersOccupied_ = outerUec.getCeiling();
outerClusterSize_ = outerTableSize / noOfOuterClustersOccupied_;
// **************************************************************************
// I am commenting the code related to left joins;this needs to be looked
// at latter. Assumption: rows are uniformly distributed across all clusters
// this is not true for example if there is a skew. Then we need to collect
// the interval information. Since that code is currently commented out, this
// is ok.
//*************************************************************************
/*if(noOfOuterClustersOccupied_ > noOfInnerClustersOccupied_)
{
// -------------------------------------------------------------------
// We don't have to flush an outer cluster in one of the two cases:
// 1. its corresponding inner cluster is empty (for non left joins).
// 2. its corresponding inner cluster is in-memory.
// -------------------------------------------------------------------
if(NOT hj_->isLeftJoin())
{
// -----------------------------------------------------------------
// We assume the maximum no of outer clusters with a corresponding
// non-empty inner cluster.
// -----------------------------------------------------------------
noOfOuterClustersFlushed_ =
MAXOF(noOfInnerClustersOccupied_ - noOfInnerClustersInMemory_, csZero);
}
else
{
// -----------------------------------------------------------------
// $$$ Here is an indication we might be able to produce the first
// $$$ row for a left join fast (which is a null-instantiated row).
// $$$ Further work needed to account for this ??
// For the time being, we assume rows can only be produced at Stage
// 2 or 3, so we still need to flush them.
// -----------------------------------------------------------------
noOfOuterClustersFlushed_ =
MAXOF(noOfOuterClustersOccupied_ - noOfInnerClustersInMemory_, csZero) ;
}
}
else
{*/
noOfOuterClustersFlushed_ =
MAXOF(noOfOuterClustersOccupied_ - noOfInnerClustersInMemory_,
csZero);
// ---------------------------------------------------------------------
// Finally, some first row cost analysis. But first, some foreword.
//
// Due to all kind of uncertainties involved in a hash operation, first
// row costs are very difficult to predict. The strategy here is to
// charge a fraction out of the LR costs as FR costs. Heuristics are
// applied to produce such fractions. Such analysis are very crude but
// I really doubt if we could come up with some thing better.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Result rows are produced when rows from the outer table probe the
// hash table of an inner cluster and find matches in the chain. The
// following ratio is useful as an estimate of how many rows from the
// outer table have probed the hash table before the first row is ever
// produced.
// ---------------------------------------------------------------------
CostScalar outerRowCountForFR = (child0RowCount_ / myRowCount_);
// ---------------------------------------------------------------------
// At least one row from the outer table is needed to produce a result
// row. Also, if this HJ sits on the right leg of a NJ, always assume
// the first row is produced in the first probe by that NJ. Otherwise,
// things get just a bit too complicated. NB: "outerRowCount" here is
// already a per-stream-per-probe (NJ probe) metric.
// ---------------------------------------------------------------------
outerRowCountForFR = (outerRowCountForFR).minCsOne();
outerRowCountForFR = MINOF(outerRowCountForFR,outerRowCount);
// ---------------------------------------------------------------------
// The fate of an outer table row can be one of these three:
//
// 1. It is thrown away at Stage 2 if it is hashed to a cluster whose
// corresponding inner cluster is empty.
// 2. It probes a hash table at Stage 2 if it is hashed to a cluster
// whose corresponding inner cluster in in-memory.
// 3. It is eventually flushed to disk if it is hashed to a cluster
// whose corresponding inner cluster has also been flushed. It is
// going to probe a hash table in Stage 3.
//
// Here, we estimate the fractions of outer table rows which fall into
// each of the above three categories.
// ---------------------------------------------------------------------
double fractionOfOuterRowProbingInStage2 =
(noOfInnerClustersInMemory_ / noOfOuterClustersOccupied_).value();
double fractionOfOuterRowProbingInStage3 =
(noOfOuterClustersFlushed_ / noOfOuterClustersOccupied_).value();
double fractionOfOuterRowsThrownAway = (1. -
fractionOfOuterRowProbingInStage2 - fractionOfOuterRowProbingInStage3);
// No of outer rows which finish their processing in Stage 2.
CostScalar outerRowsDoneInStage2 = outerRowCount *
(fractionOfOuterRowProbingInStage2 + fractionOfOuterRowsThrownAway);
// No of outer rows which finish their processing in Stage 3.
CostScalar outerRowsDoneInStage3 = outerRowCount *
fractionOfOuterRowProbingInStage3;
// Check if the first row can be produced in Stage 2.
if(outerRowCountForFR <= outerRowsDoneInStage2)
{
stage2WorkFractionForFR_ = outerRowCountForFR / outerRowsDoneInStage2;
stage3WorkFractionForFR_ = 0.;
}
else // we got to wait till Stage 3 to get our first row.
{
// No rows can be produced in Stage2.
stage2WorkFractionForFR_ = 1.;
// No of outer table rows need to be processed in Stage 3 to get FR.
outerRowCountForFR -= outerRowsDoneInStage2;
// Since all probing are done in Stage3.
stage3WorkFractionForFR_ = outerRowCountForFR / outerRowsDoneInStage3;
}
} // CostMethodHashJoin::deriveParameters().
//<pb>
// -----------------------------------------------------------------------
// CostMethodHashJoin::computeIdealCountOfClusters().
//
// This method computes the ideal no of clusters that the inner table can
// be evenly divided into such that we have the maximum cluster size that
// could satisfy the condition that:
//
// . The first cluster and a buffer from the rest of the clusters could
// stay within the memory limit during the first stage of hash join.
//
// The cluster size computed this way gives us a fair estimate of how big
// is the part of the inner table which can stay in memory in Stage 1 of
// the hash join. But there are always other considerations like whether
// the uec (of the hash keys) of the table restricts the maximum number of
// clusters the rows can be hashed to.
// -----------------------------------------------------------------------
Lng32 CostMethodHashJoin::computeIdealCountOfClusters(
const CostScalar & memoryLimit,
const CostScalar & tableSize
)
{
// We couldn't even have one cluster this way. Memory is too limited to
// do a HJ.
CMPASSERT(memoryLimit >= bufferSize_);
// If whole table fits into memory limit, just one cluster does the job.
if(memoryLimit >= tableSize) return 1;
// ---------------------------------------------------------------------
// Otherwise, the estimate is based on the solution of the set:
//
// 1. s * c = T
// 2. s + (c-1) b = M
//
// where T is the table size, M is the memory limit, c is the number of
// clusters, s is the cluster size and b is the buffer size. Formula 1
// says: the table is evenly spread among the clusters. Formula 2 says:
// one cluster together with one buffer from each remaining clusters
// just fits into the memory limits.
//
// Solving for c, we have c = ((b+M)-sqrt(sqr(b+M)-4*b*T))/(2*b), which
// doesn't have a solution if (sqr(b+M)-4*b*T)<0. In that case, we must
// overflow the whole file.
//
// Steven: There should be another solution for c, i.e.
// c = ((b+M)+sqrt(sqr(b+M)-4*b*T))/(2*b)
// But we choose the first solution, which provides a smaller answer,
// i.e. smaller number of clusters
// ---------------------------------------------------------------------
CostScalar c;
Lng32 cLong;
CostScalar bM = (memoryLimit + bufferSize_);
CostScalar bM2 = (bM * bM);
CostScalar bM2Minus4bT = bM2 - tableSize * bufferSize_ * 4.;
// Max no of clusters that could be used.
Lng32 cLongMax = (Lng32) (memoryLimit / bufferSize_).getFloor().value();
if( bM2Minus4bT.isLessThanZero() /* < csZero */)
{
// -------------------------------------------------------------------
// We don't have a solution. That means the table is so large that no
// matter how many clusters we allocate, the size of a cluster added
// to the sum of sizes of one buffer from the remaining clusters is
// always bigger than what the memory can accommodate. In that case,
// we would end up using the maximum no of clusters which can be used
// and each cluster will be flushed to disk.
// -------------------------------------------------------------------
cLong = cLongMax;
// CMPASSERT(tableSize / double(cLong) > memoryLimit);
}
else
{
c = (bM - sqrt(bM2Minus4bT.value())) / bufferSize_ / csTwo;
// This is a result of the fact that (tableSize > memoryLimit) here.
CMPASSERT(NOT c.isLessThanOne() /* >= csOne*/);
cLong = (Lng32) c.getCeiling().value();
cLong = MINOF(cLong,cLongMax);
}
return cLong;
} // CostMethodHashJoin::computeIdealCountOfClusters().
//<pb>
// -----------------------------------------------------------------------
// CostMethodHashJoin::computeInitialCountOfClusters().
//
// This method computes the no of clusters the HJ is going to use based
// on the memory limit and the estimated table size given as well as the
// initial no of buckets per cluster and buffer size.
//
// The initial number of clusters is at most 4; the method computes the value
// estimatedNumberOfOverflowClusters_. This gets used in calculating blocking
// costs of various stages of the hash join.
//
// Special case: the number of clusters is set to 1 if the join is a cross
// product; estimatedNumberOfOverflowClusters_ would reflect the memory needs
// of the cross product operation.
// -----------------------------------------------------------------------
Lng32 CostMethodHashJoin::computeInitialCountOfClusters(
const CostScalar & memoryLimit,
const CostScalar & tableSize)
{
CostScalar clusters = ( tableSize / memoryLimit);
Lng32 clustersL = (Lng32) clusters.getCeiling().value();
// there are at most 16 buckets and the initial number of clusters
// is at most 4
if (clusters > 4)
clusters = 4;
// calculate likelihood of overflow to disk; assume memoryLimit is at most
// 200 MB; memorryLimit is already in kilobytes. Note that this is only done
// for calculation of initial clusters. The number of clusters may be higher
// due to splitting
float myMemoryLimit = (float) memoryLimit.getValue();
if (memoryLimit > 2E6)
myMemoryLimit = 2E6;
estimatedNumberOfOverflowClusters_ = CostScalar ( tableSize.getValue() /
myMemoryLimit);
if (NOT hasEquiJoinPred_ && estimatedNumberOfOverflowClusters_ > 0)
{
estimatedNumberOfOverflowClusters_.operator--();
}
else if (estimatedNumberOfOverflowClusters_ > 4 )
estimatedNumberOfOverflowClusters_ = estimatedNumberOfOverflowClusters_-4;
else
estimatedNumberOfOverflowClusters_ = 0;
// At least one buffer from each bucket must fit into main memory.
CostScalar basicMemoryReqdPerCluster =
bufferSize_ * initialBucketCountPerCluster_;
CostScalar maxClusters = (memoryLimit / basicMemoryReqdPerCluster);
//long maxClustersL = (long) maxClusters.getFloor().value();
if (NOT hasEquiJoinPred_)
{
// there is only one cluster if this is a cross product
clusters=1;
}
Lng32 initialClusters = (Lng32) MINOF(clusters,maxClusters).getCeiling().value();
return initialClusters;
} // CostMethodHashJoin::computeInitialCountOfClusters().
// -----------------------------------------------------------------------
// CostMethodHashJoin::computeCreateHashTableCost().
//
// This method computes the cost of chaining up rows to form a hash table.
// The cost for computing the hash value of a row is not included in this
// computation.
// -----------------------------------------------------------------------
CostScalar CostMethodHashJoin::computeCreateHashTableCost(
const CostScalar& rowCount) const
{
return cpuCostAllocateHashTable_ + cpuCostInsertRowToChain_ * rowCount;
} // CostMethodHashJoin::computeCreateHashTableCost().
//<pb>
// -----------------------------------------------------------------------
// CostMethodHashJoin::computeTotalProbingCost().
//
// This method estimates the total CPU cost for producing the whole result
// set. Costs included here are:
//
// 1. Cost of probing a hash table by the outer rows, which includes the
// cost to traverse the chain, compare the hash keys. Also note that
// we underestimate by not considering collision here if we do find a
// match.
// 2. Cost of evaluating predicates other than the hash join predicates
// after a match has been found in 1.
// -----------------------------------------------------------------------
CostScalar CostMethodHashJoin::computeTotalProbingCost()
{
CostScalar cpu(csZero);
CostScalar compareHashKeyOpCount(csZero);
CostScalar evalOtherJoinPredsOpCount(csZero);
CostScalar evalOtherSelPredsOpCount(csZero);
// $$$ This is always the code path to take for phase 1.
if(NOT isColStatsMeaningful_)
{
// Doing cross product.
if(NOT hasEquiJoinPred_)
{
compareHashKeyOpCount = csZero;
if(hj_->isInnerNonSemiJoin())
{
evalOtherJoinPredsOpCount = csZero;
evalOtherSelPredsOpCount = resultEquiJoinRowCount_;
}
else if(hj_->isSemiJoin() OR hj_->isAntiSemiJoin())
{
evalOtherJoinPredsOpCount = resultEquiJoinRowCount_;
evalOtherSelPredsOpCount = csZero;
}
else if(hj_->isLeftJoin())
{
evalOtherJoinPredsOpCount = resultEquiJoinRowCount_;
evalOtherSelPredsOpCount = myRowCount_;
}
}
else
{
if(hj_->isInnerNonSemiJoin())
{
// ---------------------------------------------------------------
// This is the optimal case providing no collisions occur, the
// no of comparison operation necessary in total should just be
// equal to the no of rows generated by the equi-join, each row
// present in the equi-join result is a result of a successful
// key comparison operation.
// ---------------------------------------------------------------
compareHashKeyOpCount = resultEquiJoinRowCount_;
// ---------------------------------------------------------------
// An inner non-semi join has no predicates stored as joinPred().
// This is sometimes untrue when an outer join is converted to an
// inner join and its predicates copied to selectionPred() but not
// cleared.
// CMPASSERT(hj_->joinPred().isEmpty());
// ---------------------------------------------------------------
evalOtherJoinPredsOpCount = csZero;
// ---------------------------------------------------------------
// The remaining selection predicates are then evaluated on each
// row in the result of the equi-join.
// ---------------------------------------------------------------
evalOtherSelPredsOpCount = resultEquiJoinRowCount_;
}
// Semijoin and anti semijoin are costed the same.
// OA mar-02
else if(hj_->isSemiJoin() OR hj_->isAntiSemiJoin())
{
// ---------------------------------------------------------------
// No of comparisons for a semi-join is harder to estimate. It's
// because when we are probing the hash table, we may ignore the
// rest of the chain once all the remaining predicates are also
// satisfied in the match.
// ---------------------------------------------------------------
// This is a measure of selectivity of the remaining predicates.
CostScalar selOfOtherPreds =
myRowCount_ / resultEquiJoinRowCount_;
// The number of rows selected by all predicates should not
// exceed the number of rows selected only by the equijoin
// predicates. Thus, selOfOtherPreds should be a true percentage
// between zero and 1 inclusive. We force this, just in case.
//selOfOtherPreds = MAXOF(MINOF(selOfOtherPreds, csOne), csZero);
(selOfOtherPreds.maxCsOne()).minCsZero();
// ---------------------------------------------------------------
// Estimate the chain length from statistics of the inner table.
// ---------------------------------------------------------------
CostScalar averageChainLength =
child1RowCount_ / child1EquiJoinColUec_;
// Use selectivity to estimate part of chain compared.
CostScalar chainLengthTraversed = averageChainLength *
(csOne - selOfOtherPreds);
compareHashKeyOpCount = evalOtherJoinPredsOpCount =
resultEquiJoinRowCount_ * chainLengthTraversed;
// A semijoin has no predicates stored as selectionPred().
CMPASSERT(hj_->selectionPred().isEmpty());
evalOtherSelPredsOpCount = csZero;
}
else if(hj_->isLeftJoin())
{
// ---------------------------------------------------------------
// This is the optimal case providing no collisions occur, the
// no of comparison operation necessary in total should just be
// equal to the no of rows generated by the equi-join, each row
// present in the equi-join result is a result of a successful
// key comparison operation.
// ---------------------------------------------------------------
compareHashKeyOpCount = resultEquiJoinRowCount_;
// ---------------------------------------------------------------
// The rest of the join predicates are evaluated on rows produced
// by probing the hash join. A failed probe outer row go against
// the selection predicates directly.
// ---------------------------------------------------------------
evalOtherJoinPredsOpCount = resultEquiJoinRowCount_;
// ---------------------------------------------------------------
// This is just some sort of an average to estimate the no of
// rows which evaluates to TRUE on all the join predicates and
// those which get null-instantiated and go against the selection
// predicates.
// ---------------------------------------------------------------
evalOtherSelPredsOpCount =
(resultEquiJoinRowCount_ + myRowCount_) * .5;
}
}
cpu += cpuCostPositionHashTableCursor_ * child0RowCount_;
cpu += cpuCostCompareHashKeys_ * compareHashKeyOpCount;
cpu += cpuCostEvalOtherJoinPreds_ * evalOtherJoinPredsOpCount;
cpu += cpuCostEvalOtherSelPreds_ * evalOtherSelPredsOpCount;
}
return cpu;
} // CostMethodHashJoin::computeTotalProbingCost().
//<pb>
// -----------------------------------------------------------------------
// CostMethodHashJoin::computeStage1Cost().
// -----------------------------------------------------------------------
void CostMethodHashJoin::computeStage1Cost()
{
// ---------------------------------------------------------------------
// Cost scalars to be computed.
// ---------------------------------------------------------------------
CostScalar cpu(csZero), ioSeek(csZero), ioByte(csZero), ioTimeForFirstRow(csZero),
ioTimeForBlocking(csZero), ioFlushing(csZero) ; //j disk(csZero);
// ---------------------------------------------------------------------
// Steps done in Stage1.
//
// 1. Read the inner table rows and compute their hash values.
// 2. Assign the rows to clusters by their hash values.
// 3. Copy each row to a buffer associated with its cluster.
// 4. When the available memory is used up, pick a cluster and flush
// its buffers to disk.
// 5. If there are buffers left in memory after the whole inner table
// has been read, build a hash table for them by chaining the rows
// up.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Average per probe cost of computing hash value of and copying each
// row to the buffer.
// ---------------------------------------------------------------------
cpu = (cpuCostHashRow_ + cpuCostCopyChild1Row_) *
(child1RowCountPerStream_ / noOfProbesPerStream_);
// ---------------------------------------------------------------------
// Average per probe cost of flushing those clusters which cannot be
// accommodated in memory.
// ---------------------------------------------------------------------
if(noOfInnerClustersInMemory_ != noOfInnerClustersOccupied_ )
{
ioByte = clusterSizeAfterSplitsOverFlow_ *
estimatedNumberOfOverflowClusters_;
ioSeek = ioByte / bufferSize_;
ioFlushing = ioSeek * CURRSTMT_OPTDEFAULTS->getTimePerSeek() +
ioByte * CURRSTMT_OPTDEFAULTS->getTimePerSeqKb();
}
// ---------------------------------------------------------------------
// Average per probe cost to create hash table for in memory clusters.
// ---------------------------------------------------------------------
if(noOfInnerClustersInMemory_ != 0)
{
CostScalar rowsInMemory = clusterSizeAfterSplitsOverFlow_ *
noOfInnerClustersInMemory_ / extChild1RowLength_ * csOneKiloBytes;
rowsInMemory = (rowsInMemory).minCsOne();
cpu += (computeCreateHashTableCost(csZero) * noOfInnerClustersInMemory_) +
computeCreateHashTableCost(rowsInMemory);
}
// Optimizer shouldn't choose OHJ for BMOs.
// no IO cost for OHJ.
// ---------------------------------------------------------------------
// PAGE FAULT cost in case of an Ordered Hash Join that is a BMO.-OA
// ---------------------------------------------------------------------
if(CmpCommon::getDefault(COMP_BOOL_37) == DF_ON AND
hj_->isNoOverflow() AND isBMO_)
{
// Page faults possible because overflow logic is turned off.
// Since number of Clusters == 1, we have
// innerTableSize = innerClusterSize_;
double innerTableSize = innerClusterSize_.getValue();
// memoryLimit_ = Memory limit in kbytes on a per stream basis.
// No limit if zero
double percentageOfPageFaults =
(innerTableSize - memoryLimit_) /
innerTableSize;
double numPagesToScan = 1.0;
double pageSize =
CostPrimitives::getBasicCostFactor(DEF_PAGE_SIZE);
const double seekCostFR = numPagesToScan * percentageOfPageFaults;
ioTimeForFirstRow = seekCostFR * (CURRSTMT_OPTDEFAULTS->getTimePerSeek() +
pageSize * CURRSTMT_OPTDEFAULTS->getTimePerSeqKb());
// ioTimeForLastRow = ioTimeForFirstRow * noOfProbesPerStream_;
// record size of the inner table
// (add overhead per rec as the executor does, this also helps prevent
// zero divide problems)
double recordSize =
(hj_->child(1).getGroupAttr()->getRecordLength() +
CostPrimitives::getBasicCostFactor(HH_OP_HASHED_ROW_OVERHEAD)) / 1024.0;
// Find out how many rows would fit in memory
double numRowsThatFitInMemory = memoryLimit_ / recordSize;
double innerRowCount = innerTableSize / recordSize;
// ioTimeforBlocking is the no. of rows which can't fit in memory
// (waiting), times the time taken for each row, which is ioTimefor
// first row.
ioTimeForBlocking =
CostScalar((innerRowCount - numRowsThatFitInMemory)) *
ioTimeForFirstRow;
} // pagefault costing for ordered hash joins OA feb02
CostScalar cpuTimeForBlocking =
cpu * CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// If the inner table is reused, the blocking cost is divided by numprobes.
if( hj_->isReuse() AND hj_->multipleCalls() == 0)
{
CostScalar numOriginalProbes = inLogProp_->getResultCardinality();
cpuTimeForBlocking /= numOriginalProbes;
ioTimeForBlocking /= numOriginalProbes;
}
CostScalar io = ioFlushing + ioTimeForBlocking;
// ---------------------------------------------------------------------
// Synthesize the cost vectors and objects.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// the first stage
// of a HJ by a blocking cost instead of a FR and LR costs. The FR cost
// cost is changed to the BK cost, while the LR cost computation has
// been commented out below
// ---------------------------------------------------------------------
if ( CmpCommon::getDefault(COMP_BOOL_39) == DF_ON )
stage1cvBK_ =
new STMTHEAP SimpleCostVector (
cpu * CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions(),
io,
csZero, // msg time
csZero, // idle time
noOfProbesPerStream_
);
else
stage1cvBK_ =
new STMTHEAP SimpleCostVector (
cpuTimeForBlocking,
io,
csZero, // msg time
csZero, // idle time
inLogProp_->getResultCardinality()
);
} // CostMethodHashJoin::computeStage1Cost().
//<pb>
// -----------------------------------------------------------------------
// CostMethodHashJoin::computeStage2Cost().
//
// Estimates depend on overflowFraction_ computed by computeStage1Cost().
// -----------------------------------------------------------------------
void CostMethodHashJoin::computeStage2Cost()
{
// ---------------------------------------------------------------------
// Cost scalars to be computed.
// ---------------------------------------------------------------------
CostScalar cpuFR(csZero), ioSeekFR(csZero), ioByteFR(csZero);
//j, diskFR(csZero);
CostScalar cpuLR(csZero), ioSeekLR(csZero), ioByteLR(csZero);
//j diskLR(csZero);
CostScalar cpuBK(csZero);
const CostScalar numberOfProbesForHJ = inLogProp_->getResultCardinality();
// ---------------------------------------------------------------------
// Steps done in Stage2.
//
// 1. Read the outer table rows and compute their hash values.
// 2. Assign the rows to clusters by their hash values.
// 3. If a row is assigned to a cluster whose corresponding cluster for
// the inner table is in memory, a probe on the hash table occurs.
// 4. If a row is assigned to a cluster whose corresponding cluster for
// the inner table is on disk, copy the row to a buffer associated
// with this cluster.
// 5. When a buffer of a overflow cluster is full, write the buffer to
// disk and free up the space.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Average per probe cost of computing hash value of each row.
// ---------------------------------------------------------------------
CostScalar probingFractionForStage2 = ( noOfInnerClustersInMemory_ /
(noOfInnerClustersInMemory_ + noOfOuterClustersFlushed_));
if ( CmpCommon::getDefault(COMP_BOOL_54) == DF_OFF )
{
cpuLR = (cpuCostHashRow_ *
child0RowCountPerStream_ / noOfProbesPerStream_) *
probingFractionForStage2;
cpuBK = (cpuCostHashRow_ *
child0RowCountPerStream_ / noOfProbesPerStream_) *
(1 - probingFractionForStage2.getValue());
}
else
cpuLR = (cpuCostHashRow_ *
child0RowCountPerStream_ / noOfProbesPerStream_);
// ---------------------------------------------------------------------
// Outer rows that are hashed to an in-memory cluster needn't be stored
// in buffers. They could probe the hash table directly.
// ---------------------------------------------------------------------
CostScalar rowsCopied = outerClusterSize_ *
noOfOuterClustersFlushed_ / extChild0RowLength_ * csOneKiloBytes;
cpuBK += (cpuCostCopyChild0Row_ * rowsCopied);
// ---------------------------------------------------------------------
// We expect some probing activities on the hash table during Stage 2
// if there is an in-memory inner cluster.
// ---------------------------------------------------------------------
if(noOfInnerClustersInMemory_ > 0)
{
// -------------------------------------------------------------------
// The cost of such probing activities is estimated as follows:
//
// 1. We have already computed the total cost of hash table probing
// using the histograms for the *complete* set of rows and stored
// it in cpuCostTotalProbing_. See computeTotalProbingCost().
// 2. Now, we average this cost across all streams and NJ probes.
// 3. Then, we split the average between Stage 2 and Stage 3 in the
// ratio (No of clusters joined in Stage 2) to (No of clusters to
// be joined in Stage 3).
// -------------------------------------------------------------------
CostScalar cpuCostAverageProbing =
(cpuCostTotalProbing_ / countOfStreams_ / noOfProbesPerStream_);
cpuLR += (cpuCostAverageProbing * probingFractionForStage2);
}
// ---------------------------------------------------------------------
// Average per probe cost of flushing those outer clusters whose
// corresponding inner clusters are also flushed.
// ---------------------------------------------------------------------
ioByteLR = outerClusterSize_ * noOfOuterClustersFlushed_;
ioSeekLR = ioByteLR / bufferSize_;
//j diskLR = ioByteLR;
// ---------------------------------------------------------------------
// Now estimate FR cost from the LR cost and the precomputed fraction
// of LR cost to be charged as FR cost.
// ---------------------------------------------------------------------
cpuFR = cpuLR * stage2WorkFractionForFR_;
// ioByteFR = ioByteLR * stage2WorkFractionForFR_;
// ioSeekFR = ioSeekLR * stage2WorkFractionForFR_;
// diskFR = ioByteFR;
// fudge factors for cpuTime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// ---------------------------------------------------------------------
// Synthesize the cost vectors and objects.
//
// Note that memory is 0. because Stage2 didn't use additional memory.
// It reuses the memory already used by Stage1.
// ---------------------------------------------------------------------
if ( CmpCommon::getDefault(COMP_BOOL_39) == DF_ON )
stage2cvFR_ =
new STMTHEAP SimpleCostVector(
cpuFR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
else
stage2cvFR_ =
new STMTHEAP SimpleCostVector(
cpuFR * ff_cpu,
csZero,
csZero,
csZero,
numberOfProbesForHJ
);
// ---------------------------------------------------------------------
// Last row cost is a sum over all the probes. Note that disk space is
// reusable over probes and needn't be scaled.
// ---------------------------------------------------------------------
//j diskLR = ioByteLR;
cpuLR *= noOfProbesPerStream_;
ioSeekLR *= noOfProbesPerStream_;
ioByteLR *= noOfProbesPerStream_;
// no messages:
if ( CmpCommon::getDefault(COMP_BOOL_39) == DF_ON )
stage2cvLR_ =
new STMTHEAP SimpleCostVector (
cpuLR * ff_cpu,
csZero, // iotime
csZero, // msgtime
csZero, // idletime
noOfProbesPerStream_
);
else
stage2cvLR_ =
new STMTHEAP SimpleCostVector (
cpuLR * ff_cpu,
csZero, // iotime
csZero, // msgtime
csZero, // idletime
numberOfProbesForHJ
);
// -----------------------------------------------------------------------
// Writing the outer is considered blocking:
// -----------------------------------------------------------------------
// ---------------------------------------------------------------------
if ( CmpCommon::getDefault(COMP_BOOL_39) == DF_ON )
stage2cvBK_ =
new STMTHEAP SimpleCostVector (
cpuBK * ff_cpu,
ioSeekLR * CURRSTMT_OPTDEFAULTS->getTimePerSeek()+ // combined time for
ioByteLR * CURRSTMT_OPTDEFAULTS->getTimePerSeqKb(), //seeks & transfer=IO
csZero,
csZero,
noOfProbesPerStream_
);
else
stage2cvBK_ =
new STMTHEAP SimpleCostVector (
cpuBK * ff_cpu,
(ioSeekLR * CURRSTMT_OPTDEFAULTS->getTimePerSeek()+
ioByteLR * CURRSTMT_OPTDEFAULTS->getTimePerSeqKb())/numberOfProbesForHJ,
csZero,
csZero,
numberOfProbesForHJ
);
} // CostMethodHashJoin::computeStage2Cost().
//<pb>
// -----------------------------------------------------------------------
// CostMethodHashJoin::computeStage3Cost().
// -----------------------------------------------------------------------
void CostMethodHashJoin::computeStage3Cost()
{
// There is no Stage 3 if no outer clusters are flushed to disk.
if(noOfOuterClustersFlushed_ == 0)
{
stage3cvFR_ =
new STMTHEAP SimpleCostVector(
csZero,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
stage3cvLR_ =
new STMTHEAP SimpleCostVector(
csZero,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
return;
}
// ---------------------------------------------------------------------
// Cost scalars to be computed.
// ---------------------------------------------------------------------
CostScalar
cpuBK(csZero)
,cpuLR(csZero)
,cpuFR(csZero)
,ioSeekBK(csZero)
,ioByteBK(csZero)
;
// ---------------------------------------------------------------------
// In Stage 3, the remaining part of the tables are read in and joined,
// in cluster pairs. The steps are as follows:
//
// 1. Read a cluster from the inner table.
// 2. Build a hash table for the cluster read.
// 3. Read a buffer from the matching cluster in the outer table.
// 4. Probe the hash table using rows from the buffer and do the join.
// 5. Do 3 and 4 again until all buffers from that cluster are read.
// 6. Begin the cycle with another cluster.
//
// So the total cost consists of:
// A. Total I/O of reading the remaining part of both tables once.
// B. CPU cost for building the hash table for each cluster.
// C. The part of cpuCostTotalProbing_ which is not charged in Stage2.
//
// Note that the above steps assume that the smaller cluster can fit
// into memory. Otherwise, we need to read only a part of the smaller
// cluster which fits, build its hash table, probe it using the larger
// cluster, and repeat the same step for another part of the smaller
// cluster until the whole smaller cluster is exhausted. Thus, the
// larger cluster is actually read a number of times equal to ceiling(
// smallerClusterSize/memoryLimit_).
// ---------------------------------------------------------------------
CostScalar buildClusterSize = clusterSizeAfterSplitsOverFlow_;
CostScalar probeClusterSize = outerClusterSize_;
CostScalar hashLoopPasses;
// when is this more than one?
if (NOT hasEquiJoinPred_)
hashLoopPasses = estimatedNumberOfOverflowClusters_;
else
// this is always one; need to be looked into again -
hashLoopPasses = (buildClusterSize / memoryLimit_).getCeiling().value();
hashLoopPasses=hashLoopPasses.minCsOne();
// ---------------------------------------------------------------------
// Total I/O spent in reading a pair of clusters back. Hash loops mean
// the probing clusters need to be read multiple times. Cost of Item A,
// push effect of hash loops. noOfOuterClusterFlushed_ is just no of
// cluster pairs left to be joined.
// ---------------------------------------------------------------------
CostScalar probeKb,probeSeeks,buildKb,buildSeeks;
probeKb = probeClusterSize * hashLoopPasses * noOfOuterClustersFlushed_;
probeSeeks = probeKb / bufferSize_;
// probeSeeks = MINOF(CostScalar(noOfOuterClustersFlushed_), probeSeeks);
if (NOT hasEquiJoinPred_)
buildKb = buildClusterSize*estimatedNumberOfOverflowClusters_;
else
buildKb = buildClusterSize*noOfOuterClustersFlushed_;
buildSeeks = buildKb / bufferSize_;
// buildSeeks = MINOF(CostScalar(noOfOuterClustersFlushed_), buildSeeks);
// reads of probes are blocking because reads of
// I/O buffers are not using double buffering
ioByteBK = probeKb+buildKb;
ioSeekBK = probeSeeks+buildSeeks;
// ---------------------------------------------------------------------
// No of rows needed to be chained into a hash table per cluster. Note
// that we always chain rows in the inner cluster. Cost of Item B.
// ---------------------------------------------------------------------
CostScalar rowsChained = clusterSizeAfterSplitsOverFlow_ /
extChild1RowLength_ * 1024.;
cpuBK =
computeCreateHashTableCost(rowsChained)
*
estimatedNumberOfOverflowClusters_;
// ---------------------------------------------------------------------
// Remaining part of probing cost. Cost of item C.
// ---------------------------------------------------------------------
CostScalar cpuCostAverageProbing =
(cpuCostTotalProbing_ / countOfStreams_ / noOfProbesPerStream_);
double probingFractionForStage3 = ( noOfOuterClustersFlushed_ /
(noOfInnerClustersInMemory_ + noOfOuterClustersFlushed_)).value();
cpuLR += (cpuCostAverageProbing * probingFractionForStage3);
// ---------------------------------------------------------------------
// Again take the precomputed fraction of cost out of the LR cost to
// give our FR cost.
// ---------------------------------------------------------------------
cpuFR = cpuLR * stage3WorkFractionForFR_;
// fudge factor for cputime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// ---------------------------------------------------------------------
// Again all memory has been accounted for in Stage 1. Also no more
// disk space is needed.
// Also note that all I/O is blocking in stage 3, thus LR and FR
// have no I/O.
// ---------------------------------------------------------------------
// no messages:
stage3cvFR_ =
new STMTHEAP SimpleCostVector (
cpuFR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// So far, LR costs are per probe cost. Scale them up to represent all
// cost for all the probes.
// ---------------------------------------------------------------------
cpuLR *= noOfProbesPerStream_;
stage3cvLR_ =
new STMTHEAP SimpleCostVector (
cpuLR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
stage3cvBK_ =
new STMTHEAP SimpleCostVector (
cpuBK * ff_cpu,
ioSeekBK * CURRSTMT_OPTDEFAULTS->getTimePerSeek()+ // adding seektime &
ioByteBK * CURRSTMT_OPTDEFAULTS->getTimePerSeqKb(), // Kbtime to IOtime
csZero,
csZero,
noOfProbesPerStream_
);
} // CostMethodHashJoin::computeStage3Cost().
//<pb>
// -----------------------------------------------------------------------
// CostMethodHashJoin::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodHashJoin::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
deriveParameters();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// Compute costs at various stages of the Join.
// ---------------------------------------------------------------------
computeStage1Cost();
computeStage2Cost();
computeStage3Cost();
// -----------------------------------------------------------------------
// Compute first row, last row and blocking costs using temporary cost
// vectors for each of the hash join stages. Stage 1 involves building
// a hash table from the inner table and writing overflow clusters to
// disk. Stage 2 involves taking rows from the outer table, probing the
// hash table built in stage 1 and writing overflow clusters to disk.
// Stage 3 involves joining the overflow clusters from stages 1 and 2.
// Since no ancestor activity can proceed until stage 1 finishes, we put
// its resource usage in a blocking vector. Since stage 3 can not proceed
// until stage 2 completes we add the corresponding resource vectors using
// blocking addition.
// -----------------------------------------------------------------------
SimpleCostVector cvFR( blockingAdd(*stage2cvFR_, *stage3cvFR_, rpp_) );
SimpleCostVector cvLR( blockingAdd(*stage2cvLR_, *stage3cvLR_, rpp_) );
SimpleCostVector cvBK;
if (stage1cvBK_ != NULL)
{
cvBK = *stage1cvBK_;
if (stage2cvBK_ != NULL)
{
cvBK = blockingAdd(cvBK, *stage2cvBK_, rpp_);
}
if (stage3cvBK_ != NULL)
{
cvBK = blockingAdd(cvBK, *stage3cvBK_, rpp_);
}
}
// ---------------------------------------------------------------------
// Set each cost vector's number of probes.
// ---------------------------------------------------------------------
if ( CmpCommon::getDefault( COMP_BOOL_39 ) == DF_ON )
{
cvFR.setNumProbes(noOfProbesPerStream_);
cvLR.setNumProbes(noOfProbesPerStream_);
cvBK.setNumProbes(noOfProbesPerStream_);
}
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"HASHJOIN::computeOperatorCost()\n");
fprintf(pfp,"myRowCount=%g;child0RowCount=%g;child1RowCount=%g\n",
myRowCount_.value(),child0RowCount_.value(),child1RowCount_.value());
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return the cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
// ---------------------------------------------------------------------
// Create new HashJoin Cost object. Store not only the traditional Cost
// vectors and variables but also intermediate values and vectors of use
// for final costing of hash joins.
// ---------------------------------------------------------------------
Cost *costPtr = new STMTHEAP
HashJoinCost (&cvFR,
&cvLR,
&cvBK,
cpuCount,
fragmentsPerCPU,
stage2WorkFractionForFR_,
stage3WorkFractionForFR_,
stage2cvLR_,
stage3cvLR_,
stage1cvBK_,
stage2cvBK_,
stage3cvBK_
);
#ifndef NDEBUG
if ( printCost )
{
pfp = stdout;
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodHashJoin::computeOperatorCostInternal().
// -----------------------------------------------------------------------
// Clean up the cost vectors at various stages.
// -----------------------------------------------------------------------
void CostMethodHashJoin::cleanUp()
{
if (stage1cvBK_ != NULL)
{
delete stage1cvBK_;
stage1cvBK_ = NULL;
}
if (stage2cvBK_ != NULL)
{
delete stage2cvBK_;
stage2cvBK_ = NULL;
}
if (stage3cvBK_ != NULL)
{
delete stage3cvBK_;
stage3cvBK_ = NULL;
}
if (stage2cvFR_ != NULL)
{
delete stage2cvFR_;
stage2cvFR_ = NULL;
}
if (stage2cvLR_ != NULL)
{
delete stage2cvLR_;
stage2cvLR_ = NULL;
}
if (stage3cvFR_ != NULL)
{
delete stage3cvFR_;
stage3cvFR_ = NULL;
}
if (stage3cvLR_ != NULL)
{
delete stage3cvLR_;
stage3cvLR_ = NULL;
}
CostMethodJoin::cleanUp();
} // CostMethodHashJoin::cleanUp().
//<pb>
//==============================================================================
// Produce a final cumulative cost for an entire subtree rooted at a specified
// physical HASH JOIN operator.
//
// Input:
// hashJoinOp -- specified physical hash join operator.
//
// myContext -- context associated with specified physical join operator
//
// pws -- plan work space associated with specified physical hash join
// operator.
//
// planNumber -- used to get appropriate child contexts.
//
// Output:
// none
//
// Return:
// Pointer to cumulative final cost.
//
//==============================================================================
Cost*
CostMethodHashJoin::computePlanCost( RelExpr* hashJoinOp,
const Context* myContext,
const PlanWorkSpace* pws,
Lng32 planNumber)
{
//-----------------------------------------------------------------------
// Get a local copy of the required physical properties for use in later
// roll-up computations.
//-----------------------------------------------------------------------
const ReqdPhysicalProperty* rpp = myContext->getReqdPhysicalProperty();
//-------------------------------------------------------------------
// Get cumulative costs associated with each child of this hash join
// operator.
//-------------------------------------------------------------------
CostPtr leftChildCost;
CostPtr rightChildCost;
getChildCostsForBinaryOp(hashJoinOp,
myContext,
pws,
planNumber,
leftChildCost,
rightChildCost);
//---------------------------------------------------------------------------
// The vector idleT represents the amount of time during which exactly one
// of the two child processes has provided a rows to this HASH JOIN operator.
// It is a vector whose idle time component is the elapsed time of the slower
// child minus the elapsed time of the faster child.
//---------------------------------------------------------------------------
/*jo SimpleCostVector idleT;
CostScalar leftChildOpfrET = leftChildCost->getOpfr().getElapsedTime(rpp);
CostScalar rightChildOpfrET = rightChildCost->getOpfr().getElapsedTime(rpp);
idleT.setIdleTime( MAXOF( leftChildOpfrET, rightChildOpfrET )
- MINOF( leftChildOpfrET, rightChildOpfrET ) );
//-------------------------------------------------
// Add idle time calculated above to slower child.
//-------------------------------------------------
if ( leftChildOpfrET > rightChildOpfrET )
{
//---------------------------------------------------------------------
// Left child is slower. See if it or any of its descendants have any
// blocking activity.
//---------------------------------------------------------------------
if ( leftChildCost->getCpbcTotal().isZeroVector() )
{
//-----------------------------------------------------------------
// Neither the left child nor any of its descendants has any
// blocking activity, so add idle time to both the first and last
// row vectors.
//
// Remember that since the last row vector represents a cumulative
// cost per probe and the idle time represents the cost of an
// average single probe, we must repeatedly add the idle time to
// the last row vector--once for each probe. We add the first
// probe's worth of idle time using simple vector addition. Since
// all subsequent probes overlap with the previous probe, we can use
// overlapped addition for the subsequent probes.
//-----------------------------------------------------------------
leftChildCost->cpfr() += idleT;
CostScalar overlappedProbes
= leftChildCost->getCplr().getNumProbes() - 1;
leftChildCost->cplr() = overlapAdd(leftChildCost->getCplr() + idleT,
(idleT * overlappedProbes)
);
}
else
{
//------------------------------------------------------------------
// Left child or its descendents has blocking activity, so add idle
// time to the blocking vectors.
//------------------------------------------------------------------
leftChildCost->cpbc1() += idleT;
leftChildCost->cpbcTotal() += idleT;
}
}
else
{
//----------------------------------------------------------------------
// Right child is slower. See if it or any of its descendants have any
// blocking activity.
//----------------------------------------------------------------------
if ( rightChildCost->getCpbcTotal().isZeroVector() )
{
//------------------------------------------------------------------
// Neither the right child nor any of its descendants has any
// blocking activity, so add idle time to both the first and last
// row vectors.
//
// Remember that since the last row vector represents a cumulative
// cost per probe and the idle time represents the cost of an
// average single probe, we must repeatedly add the idle time to
// the last row vector--once for each probe. We add the first
// probe's worth of idle time using simple vector addition. Since
// all subsequent probes overlap with the previous probe, we can use
// overlapped addition for the subsequent probes.
//------------------------------------------------------------------
rightChildCost->cpfr() += idleT;
CostScalar overlappedProbes
= rightChildCost->getCplr().getNumProbes() - 1;
rightChildCost->cplr() = overlapAddUnary(rightChildCost->getCplr() + idleT,
(idleT * overlappedProbes)
);
}
else
{
//-------------------------------------------------------------------
// Right child or its descendents has blocking activity, so add idle
// time to the blocking vectors.
//-------------------------------------------------------------------
rightChildCost->cpbc1() += idleT;
rightChildCost->cpbcTotal() += idleT;
}
}
jo*/
//----------------------------------------------------------------------
// Get addressability to parent cost in plan workspace and roll this up
// with children costs retrieved above.
//----------------------------------------------------------------------
HashJoinCost* parentCost =
(HashJoinCost*) ((PlanWorkSpace *)pws)->getFinalOperatorCost(planNumber);
Cost* rollUpCost = new STMTHEAP Cost();
//------------------------------------------------------------------------
// For total cost, simply accumulate all resoure usage with simple vector
// addition.
//------------------------------------------------------------------------
rollUpCost->totalCost() = leftChildCost->getTotalCost()
+ rightChildCost->getTotalCost()
+ parentCost->getTotalCost();
//////////////////////////////////////////////////////////////////////////////
// The following background information will help in understanding the roll
// up formulas for first row, last row and total blocking resource usage
// vectors.
//
// A HASH JOIN consists of three stages. Stage 1 involves building a hash
// table with rows from the inner (right) table and writing overflow clusters
// to disk. Stage 2 involves taking rows from the outer (left) table, probing
// the hash table built in stage 1 and writing overflow clusters to disk.
// Stage 3 involves joining the overflow clusters from stages 1 and 2.
//
// Stage 1 resource usage comes from the total blocking vector in the hash
// join's preliminary cost object since no ancestor activity can proceed until
// stage 1 finishes.
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// No ancestor activity can begin until the left child has produced at least
// one row, so the left child's first row cost goes into the total blocking
// roll-up cost. The remaining portion of the left child's last row activity
// overlaps with stage 2, so use overlapped addition when combining the
// respective resource usage. Stage 3, however, can not proceed until stage 2
// finishes, so use blocking addition when adding resource usage from stage 3.
//
// To determine the appropriate first row activity for stages 2 and 3, use
// the same fractions calculated in preliminary costing.
//----------------------------------------------------------------------------
const SimpleCostVector & leftCplr = leftChildCost->getCplr();
const SimpleCostVector & leftCpfr = leftChildCost->getCpfr();
const SimpleCostVector & stage2Cost = parentCost->getStage2Cost();
const SimpleCostVector & stage3Cost = parentCost->getStage3Cost();
const CostScalar & stage2Fraction = parentCost->getStage2WorkFractionForFR();
const CostScalar & stage3Fraction = parentCost->getStage3WorkFractionForFR();
// *************************************************************************
// this formula assumes that leftCpfr is at most the rolled up blocking cost
// We find that this is not true. So we further adjust the cpfr() cost below
// *************************************************************************
rollUpCost->cpfr() =
blockingAdd( overlapAddUnary( leftCplr - leftCpfr, stage2Cost ) * stage2Fraction,
stage3Cost * stage3Fraction,
rpp
);
//----------------------------------------------------------------------------
// The same basic formula for first row roll-up applies for last row as well.
// Of course, we no longer need to worry about first row fractions for either
// of the two stages, so the formula is simplified accordingly.
//----------------------------------------------------------------------------
SimpleCostVector leftChildlrCostWithOutfrCost = leftCplr - leftCpfr;
if ( CmpCommon::getDefault(COMP_BOOL_56) == DF_OFF)
leftChildlrCostWithOutfrCost.setIdleTime(leftCplr.getIdleTime());
rollUpCost->cplr() = blockingAdd(overlapAddUnary(leftChildlrCostWithOutfrCost,
stage2Cost),
stage3Cost,
rpp
);
if ( CmpCommon::getDefault(COMP_BOOL_56) == DF_OFF)
{
// rolled-up last row cost should be at least the rolled up left child cost
rollUpCost->cplr() = etMAXOF(rollUpCost->cplr(), leftCplr, rpp);
}
if ( CmpCommon::getDefault(COMP_BOOL_55) == DF_OFF)
{
rollUpCost->cpbcTotal() = computeNewBlockingCost(parentCost,
leftChildCost,
rightChildCost,
rpp);
// ************************************************************
// we subtract leftchild first row roll-up cost from
// leftchild last row cost, when calculating parent's
// roll-up last row cost. If the child's first row roll-up
// cost is high, last row cost is decreasing and it is not
// our intent
// ************************************************************
if ( CmpCommon::getDefault(COMP_BOOL_56) == DF_OFF)
{
rollUpCost->cpfr() = etMINOF(rollUpCost->cpfr(),
rollUpCost->cpbcTotal(),
rpp);
}
}
else
{
//----------------------------------------------------------------------------
// Producing all rows but the first row from the right table overlaps with
// building the hash table, hence the term
//
// overlapAdd( (rightCplr - rightCpfr) / numProbes,
// parentCost->getCpbcTotal() )
//
// Note that since blocking activity is on a per probe basis, we must convert
// the last row resource usage from a cumulative cost for all probes to an
// average cost per probe.
//
// We also add in any blocking cost from the right child using blocking
// addition.
//
// Finally, the blocking activity from the left child (including the left
// child's first row cost) overlaps with blocking activity of the right
// child, so we add in the left child's blocking activity with overlapped
// addition.
//----------------------------------------------------------------------------
const SimpleCostVector & rightCplr = rightChildCost->getCplr();
const SimpleCostVector & rightCpfr = rightChildCost->getCpfr();
const CostScalar & numProbes = rightCplr.getNumProbes();
const SimpleCostVector & parentTotBl = parentCost->cpbcTotal();
const SimpleCostVector & leftChildTotBl = leftChildCost->cpbcTotal();
if ( CmpCommon::getDefault(COMP_BOOL_39) == DF_OFF )
{
// get total blocking for all probes
rollUpCost->cpbcTotal() =
overlapAddUnary(
leftCpfr + leftChildTotBl*(leftChildTotBl.getNumProbes()),
blockingAdd(
overlapAddUnary( rightCplr - rightCpfr,
parentTotBl*(parentTotBl.getNumProbes())
) + rightCpfr,
rightChildCost->getCpbcTotal()*numProbes,rpp
)
);
// scaling down blocking cost by dividing the operator blocking
// cost by the number of probes coming from HashJoin parent.
CostScalar scaleFactor = csOne/inLogProp_->getResultCardinality();
rollUpCost->cpbcTotal().scaleByValue(scaleFactor);
}
else
rollUpCost->cpbcTotal() =
overlapAddUnary(leftCpfr + leftChildCost->getCpbcTotal(),
blockingAdd( overlapAddUnary( ( rightCplr - rightCpfr)
/ numProbes,
parentCost->getCpbcTotal()
) + rightCpfr,
rightChildCost->getCpbcTotal(),
rpp
)
);
}
//----------------------------------------------------------------------------
// A compromise solution for first blocking is to simply set it equal to
// total blocking. Incorporating the first blocking cost from either leg
// unduly penalizes the activity from the other leg, so absent a more complex
// model, this compromise solution seemed the most reasonable. Since
// overlapped vectors OPFR and OPLR are not currently used, the compromise
// does no harm at this point.
//----------------------------------------------------------------------------
rollUpCost->cpbc1() = rollUpCost->getCpbcTotal();
//----------------------------------------------------------------------------
// Assuming that this HASH JOIN operator's process and its child processes
// all run in separate CPUs, no interferance occurs, so the first row produced
// by the HASH JOIN depends on the fastest of the two children and the last
// row produced by the HASH JOIN depends on the slowest of the two children.
//----------------------------------------------------------------------------
/*jo rollUpCost->opfr() = etMINOF(leftChildCost->getOpfr(),
rightChildCost->getOpfr(),
rpp);
rollUpCost->oplr() = etMAXOF(leftChildCost->getOplr(),
rightChildCost->getOplr(),
rpp);
jo*/
//-----------------------------------------------------------------------
// Child costs have been merged at this point, so delete local copies of
// those costs. The parent cost can be deleted too.
//-----------------------------------------------------------------------
delete leftChildCost;
delete rightChildCost;
delete parentCost;
//-----------------------------------------------------------------------
// In order to accommodate introduction of REUSE in hash join, the following
// changes have been made to the hash join costing:
// 1. In HashJoin::createContextForAChild, in the case of reuse, the LP returned
// take into account the reuse and use materializeInputLogProp() method. This
// ensures that the right child has the right number of probes (only ONE) and
// so it returns the right number of rows scanned.
// 2. In this method CostMethodHashJoin::computePlanCost(), the number of
// probes is set properly to reflect on the final cost. If the inner table is
// read only once, the numProbes is ONE, and in the case the inner table is read
// more than once but still reused, like in the case of ordered CharacteristicInputs,
// the numProbes is the UEC of the characteristic input fields. -OA
// ------------------------------------------------------------------------
HashJoin* hj = (HashJoin*) jn_;
if( hj->isNoOverflow() AND hj->isReuse() AND hj->multipleCalls()==0 )
{
CostScalar newNumProbes = csOne;
//Unique entry Count for the parent probes
if( NOT hj->valuesGivenToChild().isEmpty() )
{
PhysicalProperty* spp = NULL;
if ( myContext->getPlan())
spp = myContext->getPlan()->getPhysicalProperty();
CostScalar uniqueProbeCount =
inLogProp_->getAggregateUec (hj_->valuesGivenToChild());
if (uniqueProbeCount < noOfProbes_)
uniqueProbeCount = noOfProbes_;
newNumProbes = uniqueProbeCount;
CMPASSERT(spp);
if (!spp->isSorted())
{
// assume random probes with uniqueProbeCount distinct values
// hash table is rebuilt once for the first probe +
// (1 - 1/uniqueProbeCount) times for each successive probe
newNumProbes =
csOne + (noOfProbes_ - csOne) * (csOne - csOne / uniqueProbeCount);
}
}
if ( CmpCommon::getDefault(COMP_BOOL_39) == DF_ON )
{
rollUpCost->totalCost().setNumProbes(newNumProbes);
rollUpCost->cpfr().setNumProbes(newNumProbes);
rollUpCost->cplr().setNumProbes(newNumProbes);
rollUpCost->cpbcTotal().setNumProbes(newNumProbes);
rollUpCost->cpbc1().setNumProbes(newNumProbes); // this was missing
}
}
//--------------------------------------------
// Return previously calculated roll-up cost.
//--------------------------------------------
return rollUpCost;
} // CostMethodHashJoin::computePlanCost()
//*************************************************************************
// CostMethodHashJoin::computeNewBlockingCost()
// A simplified and more accurate calculation of the roll-up blocking cost.
// The previous rollup calculation has wrong assumptions when overlapping
// the blocking cost with the right child last row costs. The overlap occurs
// only for the first phase, but not for subsequent phases.
//
// The right child blocking cost and the left child blocking cost overlap;
// the blocking costs of the phase 2 and the phase 3 are added using the
// blocking addition.
//
// Also the method is rewritten to make it easier to understand.
//
//*************************************************************************
SimpleCostVector CostMethodHashJoin::computeNewBlockingCost(
HashJoinCost* parentCost,
CostPtr leftChildCost,
CostPtr rightChildCost,
const ReqdPhysicalProperty *rpp)
{
const SimpleCostVector & rightCplr = rightChildCost->getCplr();
const SimpleCostVector & leftCplr = leftChildCost->getCplr();
const SimpleCostVector & rightCpfr = rightChildCost->getCpfr();
const CostScalar & RCnumProbes = rightCplr.getNumProbes();
const CostScalar & LCnumProbes = leftCplr.getNumProbes();
const CostScalar & parentNumProbes = parentCost->
getCplr().getNumProbes();
const SimpleCostVector & stage1BK = parentCost->getStage1BKCost();
const SimpleCostVector & stage2BK = parentCost->getStage2BKCost();
const SimpleCostVector & stage3BK = parentCost->getStage3BKCost();
const SimpleCostVector & leftChildTotBl = leftChildCost->cpbcTotal();
const SimpleCostVector & rightChildTotBl=rightChildCost->cpbcTotal();
SimpleCostVector A=overlapAddUnary
(rightCplr - rightCpfr,
stage1BK * parentNumProbes) +
rightCpfr;
SimpleCostVector B=overlapAddUnary(rightChildTotBl * RCnumProbes,
leftChildTotBl*LCnumProbes);
SimpleCostVector C= blockingAdd(B, A, rpp);
SimpleCostVector D=blockingAdd(C, stage2BK*parentNumProbes, rpp);
SimpleCostVector E = blockingAdd(D, stage3BK*parentNumProbes, rpp)/parentNumProbes;
E.setNumProbes(parentNumProbes);
return E;
} // CostMethodHashJoin::computeNewBlockingCost()
//<pb>
// ----QUICKSEARCH FOR MJ.................................................
/**********************************************************************/
/* */
/* CostMethodMergeJoin */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodMergeJoin::cacheParameters().
// -----------------------------------------------------------------------
void CostMethodMergeJoin::cacheParameters(RelExpr* op,
const Context* myContext)
{
CostMethodJoin::cacheParameters(op,myContext);
mj_ = (MergeJoin*) op;
const ValueIdSet& equiJoinPreds = mj_->getEquiJoinPredicates();
cpuCostCompareKeys_ = CostPrimitives::cpuCostForCompare(equiJoinPreds);
} // CostMethodMergeJoin::cacheParameters().
//<pb>
// -----------------------------------------------------------------------
// CostMethodMergeJoin::computeIntervalMergingCost().
//
// This method computes the merging cost for a histogram interval. It
// does so by estimating how many times each of the following expressions
// are evaluated in the MJ executor:
//
// . mergeExpr, which compares a row from the left with a row from the
// right. It evaluates to TRUE if they have the same merge join key.
//
// . compExpr, which is evaluated when mergeExpr evalutes to FALSE. It
// also compares a row from the left with a row from the right and it
// evaluates to TRUE if left row has a smaller merge join key than the
// right row.
//
// . leftCheckDupExpr, which compares the join keys of the next row and
// the current row of the left child.
//
// . rightCheckDupExpr, which acts like leftCheckDupExpr for the right
// child.
//
// . preJoinExpr, which checks if a merged row matches those join preds
// other than equi-join predicates.
//
// . postJoinExpr, which checks if a joined row satisfies the selection
// remaining preds.
//
// There are the assumptions that within the interval, the rows are evenly
// distributed among the uec's and the join occurs for the maximum number
// of possible uec's (which is the smaller of the left and right uec's).
//
// -----------------------------------------------------------------------
CostScalar CostMethodMergeJoin::computeIntervalMergingCost(
CostScalar child0RowCount,
CostScalar child0Uec,
CostScalar child1RowCount,
CostScalar child1Uec,
CostScalar& mem
)
{
// Just in case. uec could never be bigger than row count.
child0Uec = MINOF(child0Uec,child0RowCount);
child1Uec = MINOF(child1Uec,child1RowCount);
// ---------------------------------------------------------------------
// The counts of no of evaluations of each expression present.
// ---------------------------------------------------------------------
CostScalar mergeExprEvalCount(csZero);
CostScalar compExprEvalCount(csZero);
CostScalar leftCheckDupExprEvalCount(csZero);
CostScalar rightCheckDupExprEvalCount(csZero);
CostScalar preJoinExprEvalCount(csZero);
CostScalar postJoinExprEvalCount(csZero);
CostScalar nullInstantiateCount(csZero);
CostScalar cpu(csZero);
// ---------------------------------------------------------------------
// For reasons that I will explain to you if you really wanna know them,
// assume that there is at least one row in each interval.
// Question from Steven: Who are you?!
// ---------------------------------------------------------------------
child0Uec = (child0Uec).minCsOne();
child0RowCount = (child0RowCount).minCsOne();
child1Uec = (child1Uec).minCsOne();
child1RowCount = (child1RowCount).minCsOne();
// ---------------------------------------------------------------------
// Find out what the predicates are to be evaluated at this HJ node,
// and compute cost primitives related to them.
// First two are just place holders. We need the last two.
// ---------------------------------------------------------------------
ValueIdSet vs1;
ValueIdSet vs2;
ValueIdSet otherJoinPreds;
ValueIdSet otherSelPreds;
classifyPredicates(vs1, vs2, otherJoinPreds, otherSelPreds);
if(otherJoinPreds.isEmpty())
cpuCostEvalOtherJoinPreds_ = csZero;
else
cpuCostEvalOtherJoinPreds_ = CostPrimitives::
cpuCostForEvalPred(otherJoinPreds);
if(otherSelPreds.isEmpty())
cpuCostEvalOtherSelPreds_ = csZero;
else
cpuCostEvalOtherSelPreds_ = CostPrimitives::
cpuCostForEvalPred(otherSelPreds);
// ---------------------------------------------------------------------
// As in Histogram analysis, assume maximum possible potential matches.
// ---------------------------------------------------------------------
CostScalar child0RowCountPerUec = (child0RowCount / child0Uec);
CostScalar child1RowCountPerUec = (child1RowCount / child1Uec);
const CostScalar & uecMatched = MINOF(child0Uec,child1Uec);
CostScalar uecUnmatched = (MAXOF(child0Uec,child1Uec)- uecMatched);
// ---------------------------------------------------------------------
// Row count assuming inner non-semi Join on the equi-join predicates.
// ---------------------------------------------------------------------
CostScalar mergeJoinRowCount =
uecMatched * child0RowCountPerUec * child1RowCountPerUec;
CostScalar unmatchedChild0RowCount =
uecUnmatched * child0RowCountPerUec;
// ---------------------------------------------------------------------
// Imagine there is a pointer on both the left and right. In all cases,
// whenever we move the pointer one row ahead, either a leftCheckDupExpr
// or a rightCheckDupExpr is then evaluated. Thus, they are evaluated a
// number of times equal to the number of rows on each side.
// ---------------------------------------------------------------------
leftCheckDupExprEvalCount = (child0RowCount - csOne);
rightCheckDupExprEvalCount = (child1RowCount - csOne);
// ---------------------------------------------------------------------
// The mergeExpr is evaluated to compare a row from the left and a row
// the right to see whether they are equal.
//
// Consider the case when mergeExpr evaluates to TRUE, that is, the key
// on the left equals that on the right, then, we produce a row and uses
// the CheckDupExpr's to move the pointers forward, producing rows along
// the way if the key values keep unchanged on both sides. (In fact, we
// first store the duplicate right rows in a list and then join all
// duplicates on the left with each row in the list. At the next time we
// apply mergeExpr again, we are comparing a different pairs of keys.
// Thus, the mergeExpr, when evaluated to TRUE, achieves the effect of
// pushing the pointers on the two sides to point to their next unique
// entry.
// ---------------------------------------------------------------------
mergeExprEvalCount = uecMatched;
// ---------------------------------------------------------------------
// When mergeExpr evaluates to FALSE, compExpr is then evaluated to
// decide which side should be moved to the next unique entry. Then,
// CheckDupExpr's on that side is evaluated to find the row with the
// next unique key. Thus, the mergeExpr, when evaluated to FALSE, has
// the effect of pushing the pointer on one side to point to its next
// unique entry together with an evaluation of compExpr. Sometimes,
// evaluation stops once one side has been exhausted. However, if the
// last unique key is a match, evaluation will occur till the very end.
// For example,
//
// +-----+ +-----+ When these two tables are merged, the sequence
// | 1 | | 1 | of expression evaluations are: 1-1M,1-3RD,
// | 2 | | 3 | 1-2LD,2-3M,2-3C,2-6LD,6-3M,6-3C,3-6RD,6-6M.
// | 6 | | 6 | mergeExpr(M) is evaluated twice to T and twice
// +-----+ +-----+ to F and Expr(C) is evaluated twice, etc.
//
// ---------------------------------------------------------------------
compExprEvalCount = uecUnmatched;
mergeExprEvalCount += uecUnmatched;
if(mj_->isLeftJoin())
{
// preJoinExpr is evaluated for each matched row from the merge join.
preJoinExprEvalCount = mergeJoinRowCount;
}
else if(mj_->isSemiJoin() OR mj_->isAntiSemiJoin())
{
// -------------------------------------------------------------------
// The pre-join predicates are evaluated on each merge-joined row we
// get until we get a TRUE. We can then skip joining this row with
// the remaining right rows having the same key value. Assume this
// point occurs on the first match.
// -------------------------------------------------------------------
preJoinExprEvalCount = (uecMatched * child0RowCountPerUec);
}
else
{
// Inner non-semi join shouldn't have any pre-join predicates.
preJoinExprEvalCount = csZero;
}
// ---------------------------------------------------------------------
// postJoinExpr is evaluated for each row output from the merge join
// and the other join predicates.
// ---------------------------------------------------------------------
if(mj_->isSemiJoin() OR mj_->isAntiSemiJoin())
{
// Semi Join should not have any post-join predicates.
CMPASSERT(mj_->selectionPred().isEmpty());
postJoinExprEvalCount = 0.;
}
else if(mj_->isInnerNonSemiJoin())
{
// -------------------------------------------------------------------
// Since its joinPred() is empty, the post join predicates are
// evaluated on every row through the merge join.
// -------------------------------------------------------------------
postJoinExprEvalCount = mergeJoinRowCount;
}
else
{
// -------------------------------------------------------------------
// Even null-instantiated rows need to be tested by post join pred.
// -------------------------------------------------------------------
postJoinExprEvalCount = mergeJoinRowCount + unmatchedChild0RowCount;
// -------------------------------------------------------------------
// Null instantiation is done on only those rows without a match in
// the hash table, and on those rows which are eliminated by other
// join preds (which are not estimated here).
// -------------------------------------------------------------------
nullInstantiateCount = unmatchedChild0RowCount;
if(mj_->nullInstantiatedOutput().isEmpty())
cpuCostNullInst_ = csZero;
else
cpuCostNullInst_ = CostPrimitives::
cpuCostForCopyRow(mj_->nullInstantiatedOutput().getRowLength());
}
// ---------------------------------------------------------------------
// All 4 expressions (mergeExpr,compExpr,leftCheckDupExpr,rightCheckDup
// Expr) involve comparing a pair of the merge key. They should cost
// more or less the same.
// ---------------------------------------------------------------------
cpu += cpuCostCompareKeys_ * (mergeExprEvalCount + compExprEvalCount +
leftCheckDupExprEvalCount + rightCheckDupExprEvalCount);
cpu += cpuCostEvalOtherJoinPreds_ * preJoinExprEvalCount;
cpu += cpuCostEvalOtherSelPreds_ * postJoinExprEvalCount;
cpu += cpuCostNullInst_ * nullInstantiateCount;
// ---------------------------------------------------------------------
// There is a cost assoicated with storing duplicate right rows in a
// list whenever there is a key match with a left row.
// ---------------------------------------------------------------------
CostScalar rowsStored = uecMatched * child1RowCountPerUec;
cpu += cpuCostInsertRowToList_ * rowsStored;
cpu += cpuCostRewindList_ * uecMatched * (child0RowCountPerUec - csOne);
cpu += cpuCostClearList_ * (uecMatched - csOne);
mem = child1RowCountPerUec * listNodeSize_;
// ---------------------------------------------------------------------
// Assume optimiscally for semi-join that the first row satisfies other
// preds.
// ---------------------------------------------------------------------
if(NOT (mj_->isSemiJoin() OR mj_->isAntiSemiJoin()))
cpu += cpuCostGetNextRowFromList_ * mergeJoinRowCount;
return cpu;
} // CostMethodMergeJoin::computeIntervalMergingCost().
//<pb>
// -----------------------------------------------------------------------
// CostMethodMergeJoin::computeTotalMergingCost()
//
// Returns a CPU cost and stores the memory cost in mem.
// -----------------------------------------------------------------------
CostScalar CostMethodMergeJoin::computeTotalMergingCost(CostScalar& mem)
{
CostScalar cpu(csZero);
// $$$ This is always the code path taken for Phase 1.
if(NOT isColStatsMeaningful_)
{
// Merge Join does not do cross product.
CMPASSERT(hasEquiJoinPred_);
// -------------------------------------------------------------------
// Do a one interval merge with the average uec.
// -------------------------------------------------------------------
// compute on per stream basis which uses skew adjusted cardinalities.
cpu = computeIntervalMergingCost(child0RowCountPerStream_,
child0UecPerStream_,
child1RowCountPerStream_,
child1UecPerStream_, mem);
}
else
{
// $$$ This code path shouldn't be taken in Phase 1.
CMPASSERT(FALSE);
// -------------------------------------------------------------------
// Traverse the histogram intervals. Compute the cost for each and
// sum up.
// -------------------------------------------------------------------
HistogramSharedPtr child0Histogram =
child0EquiJoinColStats_->getHistogram();
HistogramSharedPtr child1Histogram =
child1EquiJoinColStats_->getHistogram();
HistogramSharedPtr mergedHistogram =
mergedEquiJoinColStats_->getHistogram();
CostScalar child0Uec, child0RowCount, child1Uec, child1RowCount;
CollIndex i(1);
// Just caching some parameters needed later.
const CostScalar & child0RedFactor =
child0EquiJoinColStats_->getRedFactor();
const CostScalar & child1RedFactor =
child1EquiJoinColStats_->getRedFactor();
CostScalar memInt;
while(i < mergedHistogram->entries())
{
child0RowCount =
((*child0Histogram)[i].getCardinality() * child0RedFactor);
child1RowCount =
((*child1Histogram)[i].getCardinality() * child1RedFactor);
child0Uec = MINOF((*child0Histogram)[i].getUec(), child0RowCount);
child1Uec = MINOF((*child1Histogram)[i].getUec(), child1RowCount);
cpu += computeIntervalMergingCost(
child0RowCount,child0Uec,child1RowCount,child1Uec,memInt);
i++;
mem = MAXOF(mem,memInt);
}
}
return cpu;
} // CostMethodMergeJoin::computeTotalMergingCost().
//<pb>
// -----------------------------------------------------------------------
// CostMethodMergeJoin::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodMergeJoin::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// Compute the cost for merging the whole set, divide the cost across
// all the streams to give the average stream cost.
// ---------------------------------------------------------------------
CostScalar memLR;
CostScalar cpuMerge = computeTotalMergingCost(memLR);
CostScalar cpuLR = cpuCostPerProbeInit_ * noOfProbesPerStream_;
// ---------------------------------------------------------------------
// Just charge a fraction of the LR cost as FR cost. We cannot do any
// better in general since we don't have an idea of how rows are divided
// across streams and probes.
// $$$ Actually, first row cost can better predicted by histogram in the
// $$$ special case of a one probe serial plan.
// ---------------------------------------------------------------------
CostScalar cpuFR = cpuCostPerProbeInit_;
// per stream basis estimation
cpuLR += cpuMerge;
cpuFR += (cpuMerge / equiJnRowCountPerStream_ / noOfProbesPerStream_);
cpuLR += (cpuCostCopyAtp_ * myRowCount_ / countOfStreams_);
cpuFR += cpuCostCopyAtp_;
// Make sure the FR cost doesn't exceed per probe average of LR cost.
cpuFR = MINOF(cpuFR,cpuLR/noOfProbesPerStream_);
// fudge factor for cputime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// ---------------------------------------------------------------------
// Synthesize the cost vectors.
// ---------------------------------------------------------------------
const SimpleCostVector cvFR(
cpuFR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
const SimpleCostVector cvLR(
cpuLR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"MERGEJOIN::computeOperatorCost()\n");
fprintf(pfp,"myRowCount=%g\n",myRowCount_.value());
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP
Cost (&cvFR,&cvLR,NULL,cpuCount,fragmentsPerCPU);
#ifndef NDEBUG
if( printCost )
{
pfp = stdout;
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodMergeJoin::computeOperatorCostInternal().
//<pb>
//==============================================================================
// Produce a final cumulative cost for an entire subtree rooted at a specified
// Merge JOIN operator.
//
// Input:
// mergeJoinOp -- specified Merge JOIN operator.
//
// myContext -- context associated with specified physical join operator
//
// pws -- plan work space associated with specified physical join
// operator.
//
// planNumber -- used to get appropriate child contexts.
//
// Output:
// none
//
// Return:
// Pointer to cumulative final cost.
//
//==============================================================================
Cost*
CostMethodMergeJoin::computePlanCost( RelExpr* mergeJoinOp,
const Context* myContext,
const PlanWorkSpace* pws,
Lng32 planNumber
)
{
//----------------------------------------------------------------------
// Merge JOINs and Merge UNIONs share a general strategy with differing
// details specified in specialized virtual functions.
//----------------------------------------------------------------------
return rollUpForBinaryOp(mergeJoinOp, myContext, pws, planNumber);
} // CostMethodMergeJoin::computePlanCost()
//<pb>
//==============================================================================
// Merge cumulative costs of both children of JOIN operator when neither child
// has a blocking operator anywhere within its subtree.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// Output:
// none
//
// Return:
// Pointer to merged cost of both children.
//
//==============================================================================
Cost*
CostMethodMergeJoin::mergeNoLegsBlocking( const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp)
{
//-------------------------------------------------------------------------
// The vector idleT represents the amount of time during which exactly one
// of the two child processes has provided a rows to this JOIN operator.
// It is a vector whose idle time component is the elapsed time of the
// slower child minus the elapsed time of the faster child.
//-------------------------------------------------------------------------
/*jo SimpleCostVector idleT;
CostScalar leftChildOpfrET = leftChildCost->getOpfr().getElapsedTime(rpp);
CostScalar rightChildOpfrET = rightChildCost->getOpfr().getElapsedTime(rpp);
idleT.setIdleTime( MAXOF( leftChildOpfrET, rightChildOpfrET )
- MINOF( leftChildOpfrET, rightChildOpfrET ) );
jo*/
//-------------------------------------
// Create merged cost initially empty.
//-------------------------------------
Cost *mergedCost = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// For total cost, simply accumulate all resource usage with simple vector
// addition.
//-------------------------------------------------------------------------
mergedCost->totalCost() = leftChildCost->getTotalCost()
+ rightChildCost->getTotalCost();
//--------------------------------------------------------------------------
// First and last row computations depend on knowing which child is slower.
//--------------------------------------------------------------------------
//jo if ( leftChildCost->getOpfr().getElapsedTime(rpp)
//jo <= rightChildCost->getOpfr().getElapsedTime(rpp) )
//jo {
//------------------------
// Right child is slower.
//------------------------
//--------------------------------------------------------------------
// Since both children must produce their first row before a JION can
// produce its first row, the merged first row cost is simple the
// overlapped sum of the children's first row costs plus the idle time
// waiting for the slower of the two children (i.e. the right child in
// this case).
//--------------------------------------------------------------------
mergedCost->cpfr() = overlapAdd( leftChildCost->getCpfr(),
rightChildCost->getCpfr() ); //jo + idleT) );
//------------------------------------------------------------------
// All probes after the first probe overlap with the previous probe
// so overlappedProbes equals (number-of-probes - 1).
//------------------------------------------------------------------
//jo CostScalar overlappedProbes =
//jo (rightChildCost->getCplr().getNumProbes() - csOne);
//------------------------------------------------------------------
// Before adding idleT to the last row cost, we must convert it
// from an average cost to a total cost. The term
// idleT * overlappedProbes represents the total idle cost for
// all probes except the last probe. The term
// rightChildCost->getCplr() + idleT represents the last row cost
// of the right child plus the idle cost for the first probe. Since
// all but the first probe overlap with the previous probe, these
// two terms are added together with overlapped addition.
//------------------------------------------------------------------
mergedCost->cplr()
= overlapAdd(leftChildCost->getCplr(),rightChildCost->getCplr());
//jo overlapAdd( rightChildCost->getCplr() + idleT,
//jo idleT * overlappedProbes ) );
/*jo }
else
{
//-----------------------
// Left child is slower.
//-----------------------
//------------------------------------------------------------------------
// Since both children must produce their first row before a JION can
// produce its first row, the merged first row cost is simple the
// overlapped sum of the children's first row costs plus the idle time
// waiting for the slower of the two children (i.e. the left child in this
// case).
//------------------------------------------------------------------------
mergedCost->cpfr() = overlapAdd( (leftChildCost->getCpfr() + idleT),
rightChildCost->getCpfr() );
//------------------------------------------------------------------
// All probes after the first probe overlap with the previous probe
// so overlappedProbes equals (number-of-probes - 1).
//------------------------------------------------------------------
CostScalar overlappedProbes =
(leftChildCost->getCplr().getNumProbes() - csOne);
//------------------------------------------------------------------
// Before adding idleT to the last row cost, we must convert it
// from an average cost to a total cost. The term
// idleT * overlappedProbes represents the total idle cost for
// all probes except the last probe. The term
// leftChildCost->getCplr() + idleT represents the last row cost
// of the right child plus the idle cost for the first probe. Since
// all but the first probe overlap with the previous probe, these
// two terms are added together with overlapped addition.
//------------------------------------------------------------------
mergedCost->cplr()
= overlapAdd(rightChildCost->getCplr(),
overlapAdd( leftChildCost->getCplr() + idleT,
idleT * overlappedProbes ) );
}
//------------------------------------------------------------------------
// Assuming that this JOIN operator's process and its child processes all
// run in separate CPUs, no interferance occurs, so the first row produced
// by the JOIN depends on the fastest of the two children and the last
// row produced by the JOIN depends on the slowest of the two children.
//------------------------------------------------------------------------
mergedCost->opfr() = etMINOF( leftChildCost->getOpfr(),
rightChildCost->getOpfr(),
rpp );
mergedCost->oplr() = etMAXOF( leftChildCost->getOplr(),
rightChildCost->getOplr(),
rpp );
jo*/
return mergedCost;
} // CostMethodMergeJoin::mergeNoLegsBlocking
//<pb>
//==============================================================================
// Merge cumulative costs of both children of JOIN operator when both children
// have a blocking operator somewhere within their respective subtrees.
//
// Note: As a side effect, the right Child's blocking vectors will be
// normalized to the number of probes for the left child.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// rpp -- Parent's required physical properties needed by lower
// level routines.
//
// Output:
// none
//
// Return:
// Pointer to merged cost of both children.
//
//==============================================================================
Cost*
CostMethodMergeJoin::mergeBothLegsBlocking(const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp
)
{
//-------------------------------------
// Create merged cost initially empty.
//-------------------------------------
Cost *mergedCost = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// For total cost, simply accumulate all resource usage with simple vector
// addition.
//-------------------------------------------------------------------------
mergedCost->totalCost() = leftChildCost->getTotalCost()
+ rightChildCost->getTotalCost();
//----------------------------------------------------------------------------
// Since both children must produce their first row before a JOIN can produce
// its first row, the merged first row cost is simple the overlapped sum of
// the children's first row costs.
//----------------------------------------------------------------------------
mergedCost->cpfr() = overlapAdd( leftChildCost->getCpfr(),
rightChildCost->getCpfr() );
//-----------------------------------------------------------------
// Since both children act independently, merge last row vector as
// overlapped sum of children's last row vectors.
//-----------------------------------------------------------------
mergedCost->cplr() = overlapAdd( leftChildCost->getCplr(),
rightChildCost->getCplr() );
//--------------------------------------------------------------------
// Normalize right child's blocking vectors to left child's number of
// probes.
//--------------------------------------------------------------------
rightChildCost->cpbcTotal().normalize(
leftChildCost->getCpbcTotal().getNumProbes()
);
rightChildCost->cpbc1().normalize( leftChildCost->getCpbc1().getNumProbes() );
//-------------------------------------------------------------------------
// The vector idleT represents the amount of time during which exactly one
// of the two child processes has provided a rows to this JOIN operator.
// It is a vector whose idle time component is the elapsed time of the
// slower child minus the elapsed time of the faster child.
//-------------------------------------------------------------------------
/*jo SimpleCostVector idleT;
CostScalar leftChildOpfrET = leftChildCost->getOpfr().getElapsedTime(rpp);
CostScalar rightChildOpfrET = rightChildCost->getOpfr().getElapsedTime(rpp);
idleT.setIdleTime( MAXOF( leftChildOpfrET, rightChildOpfrET )
- MINOF( leftChildOpfrET, rightChildOpfrET ) );
jo*/
//-------------------------------------------------------------------------
// The total blocking vector formula resembles that of the last row vector
// formula with the addition of any idle time.
//-------------------------------------------------------------------------
mergedCost->cpbcTotal()
= overlapAdd( leftChildCost->getCpbcTotal(),
rightChildCost->getCpbcTotal() ); //jo + idleT;
//----------------------------------------------------------------------------
// In the formula for the current process first blocking vector, the quantity
// etMINOF(...) represents the cost of the faster child. The quantity
// vecMINOF(...) represents the interferance factor.
//----------------------------------------------------------------------------
mergedCost->cpbc1()
= overlapAdd( etMINOF( leftChildCost->getCpbc1(),
rightChildCost->getCpbc1(),
rpp ),
vecMINOF( leftChildCost->getCpbc1(),
rightChildCost->getCpbc1() ) ); //jo + idleT;
//------------------------------------------------------------------------
// Assuming that this JOIN operator's process and its child processes all
// run in separate CPUs, no interferance occurs, so the first row produced
// by the JOIN depends on the fastest of the two children and the last
// row produced by the JOIN depends on the slowest of the two children.
//------------------------------------------------------------------------
//jo mergedCost->opfr() = etMINOF( leftChildCost->getOpfr(),
//jo rightChildCost->getOpfr(),
//jo rpp );
//jo mergedCost->oplr() = etMAXOF( leftChildCost->getOplr(),
//jo rightChildCost->getOplr(),
//jo rpp );
return mergedCost;
} // CostMethodMergeJoin::mergeBothLegsBlocking
//<pb>
// ----QUICKSEARCH FOR NJ.................................................
/**********************************************************************/
/* */
/* CostMethodNestedJoin */
/* */
/**********************************************************************/
//==============================================================================
// Merge cumulative costs of both children of NESTED JOIN operator when neither
// child has a blocking operator anywhere within its subtree.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// Output:
// none
//
// Return:
// Pointer to merged cost of both children.
//
//==============================================================================
Cost*
CostMethodNestedJoin::mergeNoLegsBlocking(const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp)
{
//-------------------------------------
// Create merged cost initially empty.
//-------------------------------------
Cost *mergedCost = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// For a nested join, view left child as a unary child of the right child
// and use non-blocking unary roll-up formulas for the child merge. Since
// neither child has blocking costs, we do not merge blocking vectors and
// thus implicitly leave them as zero vectors.
//-------------------------------------------------------------------------
//------------------------------------------------
// Same formula as in non-blocking unary roll-up.
//------------------------------------------------
mergedCost->totalCost() = leftChildCost->getTotalCost()
+ rightChildCost->getTotalCost();
//------------------------------------------------------------------------
// Same formula as in non-blocking unary roll-up.
//
// The formula for CPFR relies on the assumption that the first probe in
// the right child produces the actual first row from the NLJ operator.
// When producing the first row from the NLJ operator requires multiple
// probes, all probes after the first probe overlap with the immediately
// previous probe. This implies usage of overlapped addition instead of
// blocking addition. We could also have chosen to use simple vector
// addition as a compromise, but it seemd most likely that the first probe
// would produce the first output row, so we kept blocking addition.
//------------------------------------------------------------------------
mergedCost->cpfr() = blockingAdd(leftChildCost->getCpfr(),
rightChildCost->getCpfr(),
rpp );
//------------------------------------------------
// Same formula as in non-blocking unary roll-up.
//------------------------------------------------
mergedCost->cplr() = overlapAdd(rightChildCost->getCplr(),
( leftChildCost->getCplr()
- leftChildCost->getCpfr() ) )
+ leftChildCost->getCpfr();
//-----------------------------------------------------------------
// Ensure that no component of merged first row vector exceeds the
// corresponding component of merged last row vector.
//-----------------------------------------------------------------
mergedCost->cpfr().enforceUpperBound(mergedCost->cplr());
//------------------------------------------------------------------------
// Unilke the formula for CPFR, in the formula for OPFR the compromise
// solution of simple vector addition seemed more appropriate. If the NLJ
// operator's immediate left child was an exchange node, then we would use
// blocking addition as in CPFR, but in general there could be many
// non-blocking operators between the exchange node and the left child, so
// blocking addition seemed too pessimistic.
//------------------------------------------------------------------------
//jo mergedCost->opfr() = leftChildCost->getOpfr() + rightChildCost->getOpfr();
//--------------------------------------------------------------------------
// In the formula for OPLR, the term leftChildCost->getOpfr() represents
// the initial activity needed to prime the pump. The term
//
// vecMAXOF(rightChildCost->getOplr(),
// (leftChildCost->getOplr() - leftChildCost->getOpfr()) )
//
// represents the largest resource usage between the right leg activity and
// the remaining left leg activity.
//--------------------------------------------------------------------------
//jo mergedCost->oplr() = vecMAXOF(rightChildCost->getOplr(),
//jo ( leftChildCost->getOplr()
//jo - leftChildCost->getOpfr() )
//jo )
//jo + leftChildCost->getOpfr();
return mergedCost;
} // CostMethodNestedJoin::mergeNoLegsBlocking
//<pb>
//==============================================================================
// Merge cumulative costs of both children of NESTED JOIN operator when only
// the left child has a blocking operator anywhere within its subtree.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// Output:
// none
//
// Return:
// Pointer to merged cost of both children.
//
//==============================================================================
Cost*
CostMethodNestedJoin::mergeLeftLegBlocking
( const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp
)
{
//-------------------------------------
// Create merged cost initially empty.
//-------------------------------------
Cost *mergedCost = new STMTHEAP Cost();
//--------------------------------------------------------------------------
// For a nested join, view left child as a unary child of the right child.
// Since the right leg is non-blocking, use non-blocking unary roll-up
// formulas for the child merge.
//--------------------------------------------------------------------------
//--------------------------------------------------------------
// The formulas for totalCost, CPFR and CPLR are the same as in
// non-blocking unary roll-up.
//--------------------------------------------------------------
mergedCost->totalCost() = leftChildCost->getTotalCost()
+ rightChildCost->getTotalCost();
mergedCost->cpfr() = blockingAdd(leftChildCost->getCpfr(),
rightChildCost->getCpfr(),
rpp );
mergedCost->cplr() = overlapAdd(rightChildCost->getCplr(),
( leftChildCost->getCplr()
- leftChildCost->getCpfr() ) )
+ leftChildCost->getCpfr();
//-----------------------------------------------------------------
// Ensure that no component of merged first row vector exceeds the
// corresponding component of merged last row vector.
//-----------------------------------------------------------------
mergedCost->cpfr().enforceUpperBound(mergedCost->cplr());
//--------------------------------------------------------------------------
// Since we view the left leg as logically coming underneath the right leg,
// and since by assumption only the left leg has blocking activity, CPBC1
// and CPBCTotal come from the left child after normalizing to the right
// child's number of probes.
//--------------------------------------------------------------------------
const CostScalar & rightNumProbes = rightChildCost->getCplr().getNumProbes();
mergedCost->cpbc1() =
leftChildCost->getCpbc1().getNormalizedVersion(rightNumProbes);
mergedCost->cpbcTotal() =
leftChildCost->getCpbcTotal().getNormalizedVersion(rightNumProbes);
//--------------------------------------------------------------
// The formula for OPFR is the same as when neither leg blocks.
//--------------------------------------------------------------
//jo mergedCost->opfr() = blockingAdd(leftChildCost->getOpfr(),
//jo rightChildCost->getOpfr(),
//jo rpp );
//--------------------------------------------------------------------------
// By assumption, the left leg blocks, so the right leg can not begin until
// the left leg has produced all its rows from its first probe. Thus, in
// the formula for OPLR, the term
//
// leftChildCost->getOplr() / leftOvNumProbes
//
// represents the initial activity needed to prime the pump. The term
//
// vecMAXOF(rightChildCost->getOplr(),
// (leftChildCost->getOplr()
// - (leftChildCost->getOplr() / leftOvNumProbes ) ) )
//
// represents the largest resource usage between the right leg activity and
// the remaining left leg activity.
//--------------------------------------------------------------------------
//jo const CostScalar & leftOvNumProbes = leftChildCost->getOplr().getNumProbes();
//jo mergedCost->oplr() = vecMAXOF(rightChildCost->getOplr(),
//jo ( leftChildCost->getOplr()
//jo - (leftChildCost->getOplr()
//jo / leftOvNumProbes ) )
//jo )
//jo + ( leftChildCost->getOplr() / leftOvNumProbes);
return mergedCost;
} // CostMethodNestedJoin::mergeLeftLegBlocking
//<pb>
//==============================================================================
// Merge cumulative costs of both children of NESTED JOIN operator when only
// the right child has a blocking operator anywhere within its subtree.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// Output:
// none
//
// Return:
// Pointer to merged cost of both children.
//
//==============================================================================
Cost*
CostMethodNestedJoin::mergeRightLegBlocking
( const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp
)
{
//-------------------------------------
// Create merged cost initially empty.
//-------------------------------------
Cost *mergedCost = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// For total cost, simply accumulate all resource usage with simple vector
// addition.
//-------------------------------------------------------------------------
mergedCost->totalCost() = leftChildCost->getTotalCost()
+ rightChildCost->getTotalCost();
//-------------------------------------------------------------------
// Since right child blocks, the merged first row cost is simply the
// right child's first row cost.
//-------------------------------------------------------------------
mergedCost->cpfr() = rightChildCost->getCpfr();
//-------------------------------------------------------------------------
// All of the left child's last row activity except the portion devoted to
// first row activity overlaps with the right child's last row activity.
//-------------------------------------------------------------------------
mergedCost->cplr() = overlapAdd(rightChildCost->getCplr(),
(leftChildCost->getCplr()
- leftChildCost->getCpfr()) );
//-----------------------------------------------------------------
// Ensure that no component of merged first row vector exceeds the
// corresponding component of merged last row vector.
//-----------------------------------------------------------------
mergedCost->cpfr().enforceUpperBound(mergedCost->cplr());
//-------------------------------------------------------------------------
// Since the right child's blocking activity can't begin until it receives
// one row from the left child, we add the left child's first row activity
// to both of the right child's blocking vectors.
//-------------------------------------------------------------------------
const CostScalar & rightNumProbes =
rightChildCost->getCpbcTotal().getNumProbes();
SimpleCostVector blkLeftCpfr = leftChildCost->getCpfr() / rightNumProbes;
mergedCost->cpbc1() = blockingAdd(rightChildCost->getCpbc1(),
blkLeftCpfr,
rpp);
mergedCost->cpbcTotal() = blockingAdd(rightChildCost->getCpbcTotal(),
blkLeftCpfr,
rpp);
//---------------------------------------------------------------------
// The formulas for OPFR and OPLR are the same as when neither leg has
// any blocking activity.
//---------------------------------------------------------------------
//jo mergedCost->opfr() = leftChildCost->getOpfr() + rightChildCost->getOpfr();
//jo mergedCost->oplr() = vecMAXOF(rightChildCost->getOplr(),
//jo ( leftChildCost->getOplr()
//jo - leftChildCost->getOpfr() )
//jo )
//jo + leftChildCost->getOpfr();
return mergedCost;
} // CostMethodNestedJoin::mergeRightLegBlocking
//<pb>
//==============================================================================
// Merge cumulative costs of both children of NESTED JOIN operator when both
// children have a blocking operator somewhere within their respective subtrees.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// rpp -- Parent's required physical properties needed by lower
// level routines.
//
// Output:
// none
//
// Return:
// Pointer to merged cost of both children.
//
//==============================================================================
Cost*
CostMethodNestedJoin::mergeBothLegsBlocking(
const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp
)
{
//-------------------------------------
// Create merged cost initially empty.
//-------------------------------------
Cost *mergedCost = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// For total cost, simply accumulate all resource usage with simple vector
// addition.
//-------------------------------------------------------------------------
mergedCost->totalCost() = leftChildCost->getTotalCost()
+ rightChildCost->getTotalCost();
//-------------------------------------------------------------------
// Since right child blocks, the merged first row cost is simply the
// right child's first row cost.
//-------------------------------------------------------------------
mergedCost->cpfr() = rightChildCost->getCpfr();
//-------------------------------------------------------------------------
// All of the left child's last row activity except the portion devoted to
// first row activity overlaps with the right child's last row activity.
//-------------------------------------------------------------------------
mergedCost->cplr() = overlapAdd(rightChildCost->getCplr(),
(leftChildCost->getCplr()
- leftChildCost->getCpfr()) );
//-----------------------------------------------------------------
// Ensure that no component of merged first row vector exceeds the
// corresponding component of merged last row vector.
//-----------------------------------------------------------------
mergedCost->cpfr().enforceUpperBound(mergedCost->cplr());
//-----------------------------------------------------------------------
// Determine right child's number of probes for normalizing left child's
// blocking vectors appropriately.
//-----------------------------------------------------------------------
const CostScalar & rightNumProbes =
rightChildCost->getCpbcTotal().getNumProbes();
//-----------------------------------------------------------------------
// Since left child has blocking activity, it's lowest blocking operator
// becomes the merged lowest blocking operator.
//-----------------------------------------------------------------------
mergedCost->cpbc1() =
leftChildCost->getCpbc1().getNormalizedVersion(rightNumProbes);
//--------------------------------------------------------------------------
// The right child's blocking activity can't begin until the left child has
// completed all of its blocking activity and returned at least one row.
// Thus, we combine the left child's total blocking activity, the left
// child's first row activity and the right child's total blocking activity
// to produced merged total blocking activity.
//--------------------------------------------------------------------------
SimpleCostVector normLeftCpbcTotal =
leftChildCost->getCpbcTotal().getNormalizedVersion(rightNumProbes);
SimpleCostVector blkLeftCpfr = leftChildCost->getCpfr() / rightNumProbes;
mergedCost->cpbcTotal() =
blockingAdd(blockingAdd(rightChildCost->getCpbcTotal(),
blkLeftCpfr,
rpp),
normLeftCpbcTotal,
rpp);
//---------------------------------------------------------------------
// The formulas for OPFR and OPLR are the same as when only the left
// leg has any blocking activity.
//---------------------------------------------------------------------
//jo mergedCost->opfr() = blockingAdd(leftChildCost->getOpfr(),
//jo rightChildCost->getOpfr(),
//jo rpp );
//jo const CostScalar & leftOvNumProbes = leftChildCost->getOplr().getNumProbes();
//jo mergedCost->oplr() = vecMAXOF(rightChildCost->getOplr(),
//jo ( leftChildCost->getOplr()
//jo - (leftChildCost->getOplr()
//jo / leftOvNumProbes ) )
//jo )
//jo + ( leftChildCost->getOplr() / leftOvNumProbes);
return mergedCost;
} //CostMethodNestedJoin::mergeBothLegsBlocking
//<pb>
// -----------------------------------------------------------------------
// CostMethodNestedJoin::cacheParameters()
// -----------------------------------------------------------------------
void CostMethodNestedJoin::cacheParameters(RelExpr* op,
const Context* myContext)
{
CostMethodJoin::cacheParameters(op,myContext);
nj_ = (NestedJoin*) op;
// ---------------------------------------------------------------------
// All predicates on a NJ are pushable to the right leg, except in the
// case of a left join. Some of its selectionPred() may be needed to be
// evaluated at the NJ node itself after null-instantiation. In that
// case, it's ok to push down the part of selectionPred() which can be
// pushed down to the left, but not so for the right. Otherwise, we can
// end up returning a null-instantiated row, which should be eliminated
// by the selectionPred().
// ---------------------------------------------------------------------
if(nj_->isLeftJoin() AND (NOT nj_->selectionPred().isEmpty()))
{
cpuCostEvalPred_ = CostPrimitives::cpuCostForEvalPred(
nj_->selectionPred());
}
else
{
cpuCostEvalPred_ = csZero;
}
// ---------------------------------------------------------------------
// If it's a left join, there might be a possibility that we need to
// null-instantiate a row "explicitly". In that case, we need to account
// for the CPU cost and extra buffers.
// ---------------------------------------------------------------------
if(nj_->isLeftJoin() AND (NOT nj_->nullInstantiatedOutput().isEmpty()))
{
cpuCostNullInst_ = CostPrimitives::cpuCostForCopyRow(
nj_->nullInstantiatedOutput().getRowLength());
}
else
{
cpuCostNullInst_ = csZero;
}
}
//<pb>
// -----------------------------------------------------------------------
// CostMethodNestedJoin::computeOperatorCostInternal()
// -----------------------------------------------------------------------
Cost*
CostMethodNestedJoin::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// CostScalars to be computed.
// ---------------------------------------------------------------------
CostScalar cpuFR(csZero), cpuLR(csZero), mem(csZero);
// ---------------------------------------------------------------------
// Work in NJ operator is divided into three phases.
//
// In the first phase, a minimal cost is incurred in passing request
// from its parent to its left child plus some book-keeping cost for
// handling multiple requests.
//
// Phase two involves passing the atp's from its the left queue to its
// right queue, which is done for each row returned from the left.
//
// In the last phase, the predicates are evaluated, and the ATPs of the
// satisfying rows are copied to the parent queue.
//
// Below, we are considering the total cost for all probes on all
// streams, and then amortize the resulting cost over the streams.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Phase 1 cost.
// ---------------------------------------------------------------------
CostScalar noOfProbesNeededToGetFirstRow =
(noOfProbes_/myRowCount_).minCsOne();
if(nj_->isLeftJoin() AND nj_->selectionPred().isEmpty())
noOfProbesNeededToGetFirstRow = csOne;
noOfProbesNeededToGetFirstRow =
MINOF(noOfProbesNeededToGetFirstRow,noOfProbes_);
cpuFR += cpuCostPerProbeInit_ * noOfProbesNeededToGetFirstRow;
cpuLR += cpuCostPerProbeInit_ * noOfProbes_;
// ---------------------------------------------------------------------
// Phase 2 cost.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// This is an estimate of the no of rows passed to the right to get the
// first row returned.
// ---------------------------------------------------------------------
CostScalar leftRowsNeededToGetFirstRow =
MIN_ONE(child0RowCount_/myRowCount_);
if(nj_->isLeftJoin() AND nj_->selectionPred().isEmpty())
leftRowsNeededToGetFirstRow = csOne;
leftRowsNeededToGetFirstRow =
MINOF(leftRowsNeededToGetFirstRow,child0RowCount_);
cpuFR += cpuCostPassRow_ * leftRowsNeededToGetFirstRow;
cpuLR += cpuCostPassRow_ * child0RowCount_;
// ---------------------------------------------------------------------
// Phase 3 cost.
// ---------------------------------------------------------------------
if( NOT cpuCostEvalPred_.isZero() )
{
CostScalar rightRowsNeededToGetFirstRow =
(child1RowCount_/myRowCount_).minCsOne();
rightRowsNeededToGetFirstRow =
MINOF(rightRowsNeededToGetFirstRow,child1RowCount_);
cpuFR += (cpuCostEvalPred_ * rightRowsNeededToGetFirstRow);
cpuLR += (cpuCostEvalPred_ * child1RowCount_);
}
// ---------------------------------------------------------------------
// If it's a left join, there might be a possibility that we need to
// null-instantiate a row "explicitly". In that case, we need to account
// for the CPU cost and extra buffers.
// $$$ This is very crude estimate. Can interface better with Logical
// $$$ property synthesis to get better estimate using Left Join stats.
// ---------------------------------------------------------------------
if( NOT cpuCostNullInst_.isZero() )
{
if(child1RowCount_ < child0RowCount_)
{
cpuFR += cpuCostNullInst_ * countOfStreams_;
cpuLR += cpuCostNullInst_ *
(child0RowCount_ - child1RowCount_).minCsOne();
}
// -------------------------------------------------------------------
// Buffers are only allocated to hold the rows which are "explicitly"
// null-instantiated. Otherwise, NLJ needs negligible memory to run.
// -------------------------------------------------------------------
mem = CostScalar(bufferCount_ * bufferSize_);
}
// ---------------------------------------------------------------------
// Finally, account for the cost of passing the row to the parent.
// ---------------------------------------------------------------------
cpuFR += cpuCostPassRow_ * countOfStreams_;
cpuLR += cpuCostPassRow_ * myRowCount_;
// ---------------------------------------------------------------------
// Amortize the cpu costs across multiple streams of execution.
// ---------------------------------------------------------------------
cpuFR /= countOfStreams_;
cpuLR /= countOfStreams_;
// ---------------------------------------------------------------------
// Compute the per-probe average FR cost. Make sure it doesn't exceed
// the per-probe average of the LR cost.
// ---------------------------------------------------------------------
cpuFR /= noOfProbesPerStream_;
cpuFR = MINOF(cpuFR,cpuLR/noOfProbesPerStream_);
//fudge factor for cputime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// ---------------------------------------------------------------------
// Synthesize the cost vectors.
// ---------------------------------------------------------------------
const SimpleCostVector cvFR(
cpuFR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
const SimpleCostVector cvLR(
cpuLR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"NESTEDJOIN::computeOperatorCost()\n");
fprintf(pfp,"myRowCount=%g;child0RowCount=%g;child1RowCount=%g\n",
myRowCount_.value(),child0RowCount_.value(),child1RowCount_.value());
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP
Cost (&cvFR,&cvLR,NULL,cpuCount,fragmentsPerCPU);
#ifndef NDEBUG
if ( printCost )
{
pfp = stdout;
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodNestedJoin::computeOperatorCostInternal().
//<pb>
//==============================================================================
// Produce a final cumulative cost for an entire subtree rooted at a specified
// physical NESTED LOOPS JOIN operator.
//
// Input:
// nestedJoinOp -- specified physical nested loops join operator.
//
// myContext -- context associated with specified physical nested join
// operator.
//
// pws -- plan work space associated with specified physical join
// operator.
//
// planNumber -- used to get appropriate child contexts.
//
// Output:
// none
//
// Return:
// Pointer to cumulative final cost.
//
//==============================================================================
Cost*
CostMethodNestedJoin::computePlanCost( RelExpr* nestedJoinOp,
const Context* myContext,
const PlanWorkSpace* pws,
Lng32 planNumber)
{
//--------------------------------------------------------------------------
// Get cumulative costs associated with each child of this nested loops
// join operator.
//--------------------------------------------------------------------------
CostPtr leftChildCost;
CostPtr rightChildCost;
getChildCostsForBinaryOp( nestedJoinOp
, myContext
, pws
, planNumber
, leftChildCost
, rightChildCost);
//--------------------------------------------------------------------------
// For index joins we set the priority of the right child to normal
// priority in order to to double count the scan priority when we combine
// the children priorities.
//--------------------------------------------------------------------------
if((nestedJoinOp->getGroupAttr()->getNumBaseTables() == 1) AND
(nestedJoinOp->child(0).getGroupAttr()->getNumBaseTables() == 1))
{
// We right on an Index_Join. Do the correction.
rightChildCost->planPriority().resetToNormal();
// Reset also original copy of cost object
// Fix for coverity cid 1096: NULL_RETURNS
Context * childContext = pws->getChildContext( 1, planNumber);
if (childContext != NULL)
((Cost*) (childContext->getSolution()->getRollUpCost()))
->planPriority().resetToNormal();
}
//--------------------------------------------------------------------------
// Merging of children's costs depend on which (if any) children of this
// nested loops join operator have blocking costs.
//--------------------------------------------------------------------------
Cost* mergedChildCost;
const ReqdPhysicalProperty* rpp = myContext->getReqdPhysicalProperty();
if ( leftChildCost->getCpbcTotal().isZeroVectorWithProbes() )
{
if ( rightChildCost->getCpbcTotal().isZeroVectorWithProbes() )
{
//-------------------------------------------------------
// Neither child has a blocking operator in its subtree.
//-------------------------------------------------------
mergedChildCost = mergeNoLegsBlocking(leftChildCost,
rightChildCost,
rpp);
}
else
{
//----------------------------------------------------------
// Only right child has a blocking operator in its subtree.
//----------------------------------------------------------
mergedChildCost = mergeRightLegBlocking(leftChildCost,
rightChildCost,
rpp);
}
}
else
{
if ( rightChildCost->getCpbcTotal().isZeroVectorWithProbes() )
{
//---------------------------------------------------------
// Only left child has a blocking operator in its subtree.
//---------------------------------------------------------
mergedChildCost = mergeLeftLegBlocking(leftChildCost,
rightChildCost,
rpp);
}
else
{
//----------------------------------------------------------
// Both children have blocking operators in their subtrees.
//----------------------------------------------------------
mergedChildCost = mergeBothLegsBlocking(leftChildCost,
rightChildCost,
rpp);
}
}
//-----------------------------------------------------------------------
// Child costs have been merged at this point, so delete local copies of
// those costs.
//-----------------------------------------------------------------------
delete leftChildCost;
delete rightChildCost;
//----------------------------------------------------------------------
// Get addressability to parent cost in plan workspace and roll this up
// with the recently calculated merged children cost.
//----------------------------------------------------------------------
Cost* parentCost = ((PlanWorkSpace *)pws)->getFinalOperatorCost(planNumber);
Cost* rollUpCost = rollUp(parentCost, mergedChildCost, rpp);
//----------------------------------------------------------------------
// The parent cost and the local copy of merged child cost have been
// rolled up at this point, so delete them.
//----------------------------------------------------------------------
delete mergedChildCost;
delete parentCost;
//--------------------------------------------
// Return previously calculated roll-up cost.
//--------------------------------------------
return rollUpCost;
} // CostMethodNestedJoin::computePlanCost()
//<pb>
// ----QUICKSEARCH FOR NJF................................................
/**********************************************************************/
/* */
/* CostMethodNestedJoinFlow */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodNestedJoinFlow::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodNestedJoinFlow::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// The NestedJoinFlow operator doesn't produce a row, it is just used
// to pass row from the left to the right. It doesn't do anything to
// any row produced by the right.
// ---------------------------------------------------------------------
CostScalar cpuLR = (cpuCostPassRow_ * child0RowCountPerStream_);
// ---------------------------------------------------------------------
// First row cost is just the cost for the complete probe, since NJF
// doesn't produce any rows.
// ---------------------------------------------------------------------
CostScalar cpuFR = cpuLR / noOfProbesPerStream_;
//fudge factor for cputime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// ---------------------------------------------------------------------
// Synthesize the cost vectors.
// ---------------------------------------------------------------------
const SimpleCostVector cvFR(
cpuFR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
const SimpleCostVector cvLR(
cpuLR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"NESTEDJOINFLOW::computeOperatorCost()\n");
fprintf(pfp,"childRowCount=%g",child0RowCount_.value());
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP
Cost (&cvFR,&cvLR,NULL,cpuCount,fragmentsPerCPU);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodNestedJoinFlow::computeOperatorCostInternal().
//<pb>
// ----QUICKSEARCH FOR MU.................................................
/**********************************************************************/
/* */
/* CostMethodMergeUnion */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodMergeUnion::cacheParameters().
// -----------------------------------------------------------------------
void CostMethodMergeUnion::cacheParameters(
RelExpr* op, const Context* myContext)
{
CostMethod::cacheParameters(op,myContext);
mu_ = (MergeUnion*) op;
ValueIdSet sortKeyVis;
sortKeyVis.insertList(mu_->getSortOrder());
cpuCostCopyRow_ = CostPrimitives::cpuCostForCopySet(myVis());
cpuCostCompareKeys_ = (sortKeyVis.isEmpty() ?
csZero : CostPrimitives::cpuCostForCompare(sortKeyVis));
}
//<pb>
// -----------------------------------------------------------------------
// CostMethodMergeUnion::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodMergeUnion::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// CostScalars to be computed.
// ---------------------------------------------------------------------
CostScalar cpuFR(csZero), cpuLR(csZero), mem(csZero);
// ---------------------------------------------------------------------
// Some start up cost.
// ---------------------------------------------------------------------
cpuFR += cpuCostPerProbeInit_;
cpuLR += cpuCostPerProbeInit_ * noOfProbes_;
// ---------------------------------------------------------------------
// Assume left and right children progresses as same rate for FR cost.
// When a sort order is required, we need a row from both left and right
// to produce one row.
// ---------------------------------------------------------------------
if( cpuCostCompareKeys_.isZero() )
cpuFR += cpuCostCopyAtp_ + cpuCostCopyRow_;
else
cpuFR += (cpuCostCopyAtp_ + cpuCostCopyRow_) * 2 + cpuCostCompareKeys_;
// ---------------------------------------------------------------------
// To pass the result back to up queue to parent.
// ---------------------------------------------------------------------
cpuFR += cpuCostCopyAtp_ * csTwo;
// ---------------------------------------------------------------------
// Processing cost includes copying each row from left and right to the
// buffer, evaluating the merge expression if there is one, and copy the
// ATP to its up queue.
// ---------------------------------------------------------------------
cpuLR += (cpuCostCopyRow_ + cpuCostCompareKeys_ + cpuCostCopyAtp_) *
myRowCount_;
// ---------------------------------------------------------------------
// The merge union operator allocates a buffer pool of five buffers,
// each of size 10024 bytes (yes, 10024 bytes, ie. 1033.7891 kbytes) at
// the beginning. It sticks with using only these buffers thereafter.
// ---------------------------------------------------------------------
mem = CostScalar(bufferSize_ * bufferCount_);
// ---------------------------------------------------------------------
// Average the LR cost across all the streams available. The FR cost
// has been computed based on on a probe in a single stream and needn't
// been averaged out. However, we don't want the per probe average of
// the FR cost to be higher than that of the last row cost and which may
// happen when we average the LR cost out.
// ---------------------------------------------------------------------
cpuLR /= countOfStreams_;
cpuFR = MINOF(cpuFR,cpuLR/noOfProbesPerStream_);
//fudge factor for cputime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// ---------------------------------------------------------------------
// Synthesize the cost vectors.
// ---------------------------------------------------------------------
const SimpleCostVector cvFR (
cpuFR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
const SimpleCostVector cvLR (
cpuLR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"MERGEUNION::computeOperatorCost()\n");
fprintf(pfp,"myRowCount=%g\n",myRowCount_.value());
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP
Cost (&cvFR,&cvLR,NULL,cpuCount,fragmentsPerCPU);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodMergeUnion::computeOperatorCostInternal().
//<pb>
//==============================================================================
// Produce a final cumulative cost for an entire subtree rooted at a specified
// physical UNION operator.
//
// Input:
// unionOp -- specified physical union operator.
//
// myContext -- context associated with specified physical union operator
//
// pws -- plan work space associated with specified physical union
// operator.
//
// planNumber -- used to get appropriate child contexts.
//
// Output:
// none
//
// Return:
// Pointer to cumulative final cost.
//
//==============================================================================
Cost*
CostMethodMergeUnion::computePlanCost( RelExpr* unionOp,
const Context* myContext,
const PlanWorkSpace* pws,
Lng32 planNumber
)
{
//----------------------------------------------------------------------
// For now, UNIONs use a generic roll-up strategy for binary operators.
//----------------------------------------------------------------------
return rollUpForBinaryOp(unionOp, myContext, pws, planNumber);
} // CostMethodMergeUnion::computePlanCost()
//<pb>
//==============================================================================
// Merge cumulative costs of both children of UNION operator when neither child
// has a blocking operator anywhere within its subtree.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// rpp -- Parent's required physical properties needed by lower
// level routines.
//
// Output:
// none
//
// Return:
// Pointer to merged cost of both children.
//
//==============================================================================
Cost*
CostMethodMergeUnion::mergeNoLegsBlocking( const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp
)
{
//-------------------------------------
// Create merged cost initially empty.
//-------------------------------------
Cost* mergedCost = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// For total cost, simply accumulate all resource usage with simple vector
// addition.
//-------------------------------------------------------------------------
mergedCost->totalCost() = leftChildCost->getTotalCost()
+ rightChildCost->getTotalCost();
//---------------------------------------------------------------------------
// The first row cost is the cost of the faster child plus a measure of
// interferance between the two children. The quantity etMINOF(...)
// represents the cost of the faster child. The quantity vecMINOF(...)
// represents the interferance factor.
//---------------------------------------------------------------------------
mergedCost->cpfr() = overlapAdd( etMINOF( leftChildCost->getCpfr(),
rightChildCost->getCpfr(),
rpp ),
vecMINOF( leftChildCost->getCpfr(),
rightChildCost->getCpfr() ) );
//------------------------------------------------------------------------
// Last row cost requires both children to finish, so add both children's
// cost using overlapped addition.
//------------------------------------------------------------------------
mergedCost->cplr() = overlapAdd( leftChildCost->getCplr(),
rightChildCost->getCplr() );
//-----------------------------------------------------------------
// Ensure that no component of merged first row vector exceeds the
// corresponding component of merged last row vector.
//-----------------------------------------------------------------
mergedCost->cpfr().enforceUpperBound(mergedCost->cplr());
//-------------------------------------------------------------------------
// Assuming that this UNION operator's process and its child processes all
// run in separate CPUs, no interferance occurs, so the first row produced
// by the UNION depends on the fastest of the two children and the last
// row produced by the UNION depends on the slowest of the two children.
//-------------------------------------------------------------------------
//jo mergedCost->opfr() = etMINOF( leftChildCost->getOpfr(),
//jo rightChildCost->getOpfr(),
//jo rpp );
//jo mergedCost->oplr() = etMAXOF( leftChildCost->getOplr(),
//jo rightChildCost->getOplr(),
//jo rpp );
return mergedCost;
} // CostMethodMergeUnion::mergeNoLegsBlocking
//<pb>
//==============================================================================
// Merge cumulative costs of both children of JOIN operator when both children
// have a blocking operator somewhere within their respective subtrees.
//
// Note: As a side effect, the right Child's blocking vectors will be
// normalized to the number of probes for the left child.
//
// Input:
// leftChildCost -- pointer to cumulative cost of left child.
//
// rightChildCost -- pointer to cumulative cost of right child.
//
// rpp -- Parent's required physical properties needed by lower
// level routines.
//
// Output:
// none
//
// Return:
// Pointer to merged cost of both children.
//
//==============================================================================
Cost*
CostMethodMergeUnion::mergeBothLegsBlocking(
const CostPtr leftChildCost,
const CostPtr rightChildCost,
const ReqdPhysicalProperty* const rpp
)
{
Cost *mergedCost = new STMTHEAP Cost();
//-------------------------------------------------------------------------
// For total cost, simply accumulate all resource usage with simple vector
// addition.
//-------------------------------------------------------------------------
mergedCost->totalCost() = leftChildCost->getTotalCost()
+ rightChildCost->getTotalCost();
//---------------------------------------------------------------------------
// The first row cost is the cost of the faster child plus a measure of
// interferance between the two children. The quantity etMINOF(...)
// represents the cost of the faster child. The quantity vecMINOF(...)
// represents the interferance factor.
//---------------------------------------------------------------------------
mergedCost->cpfr() = overlapAdd( etMINOF( leftChildCost->getCpfr(),
rightChildCost->getCpfr(),
rpp ),
vecMINOF( leftChildCost->getCpfr(),
rightChildCost->getCpfr() ) );
//---------------------------------------------------------------------
// Normalize right child's blocking vector's to left child's number of
// probes.
//---------------------------------------------------------------------
rightChildCost->cpbcTotal().normalize(
leftChildCost->getCpbcTotal().getNumProbes()
);
rightChildCost->cpbc1().normalize( leftChildCost->getCpbc1().getNumProbes() );
//-------------------------------------------------------------------------
// Last row cost calculation changes slightly depending on which child has
// slower blocking activity.
//-------------------------------------------------------------------------
CostScalar leftChildCpbcTotalET =
leftChildCost->getCpbcTotal().getElapsedTime(rpp);
CostScalar rightChildCpbcTotalET =
rightChildCost->getCpbcTotal().getElapsedTime(rpp);
const CostScalar & leftChildCpbcTotalNumProbes =
leftChildCost->getCpbcTotal().getNumProbes();
const CostScalar & rightChildCpbcTotalNumProbes =
rightChildCost->getCpbcTotal().getNumProbes();
if ( leftChildCpbcTotalET <= rightChildCpbcTotalET )
{
//---------------------------------------------------------------------
// Right child has slower blocking activity. Calculate a ratio which
// represents the percentage of the left child's last row activity that
// overlaps with the right child's blocking activity.
//---------------------------------------------------------------------
const CostScalar leftChildLastRowElapsedTime =
leftChildCost->getCplr().getElapsedTime(rpp); // div-by-zero fix
// if leftChildLastRowElapsedTime is zero (for whatever reason ... this is
// suspicious ... ), set the ratio to be one -- don't divide by zero!
const CostScalar overlapRatio =
(leftChildLastRowElapsedTime.isZero())
? csOne
: MINOF( csOne,
( ( rightChildCpbcTotalET * rightChildCpbcTotalNumProbes )
- ( leftChildCpbcTotalET * leftChildCpbcTotalNumProbes )
) / leftChildLastRowElapsedTime
);
//-------------------------------------------------------------------
// Produce a vector which represents the portion of the left child's
// last row activity that does not overlap with the right child's
// blocking activity.
//-------------------------------------------------------------------
SimpleCostVector nonOverlapLeft = leftChildCost->getCplr();
nonOverlapLeft.scaleByValue( csOne - overlapRatio );
//----------------------------------------------------------------------
// Multiply right child's blocking vector by its number of probes so it
// represents cumulative activity over all probes and is thus
// commensurate with last row activity.
//----------------------------------------------------------------------
SimpleCostVector rightBlockingAllProbes =
rightChildCost->getCpbcTotal() * rightChildCpbcTotalNumProbes;
//------------------------------------------------------------------------
// Produce a vector which represents the portion of the left child's last
// row activiity that overlaps with the right child's blocking activity.
// Reduce this vector to the extent by which it actually overlaps.
//------------------------------------------------------------------------
SimpleCostVector overlapLeft = leftChildCost->getCplr();
overlapLeft = overlapAdd(overlapLeft.scaleByValue(overlapRatio),
rightBlockingAllProbes)
- rightBlockingAllProbes;
//-----------------------------------------------------------------------
// Since both children of a UNION act independently, merge the last row
// vectors of both children using overlapped addition. A portion of the
// left child's last row activity has been reduced to the extent that it
// can overlap with the right child's blocking activity.
//-----------------------------------------------------------------------
mergedCost->cplr() = overlapAdd(rightChildCost->getCplr(),
nonOverlapLeft + overlapLeft);
}
else
{
//----------------------------------------------------------------------
// Left child has slower blocking activity. Calculate a ratio which
// represents the percentage of the right child's last row activity that
// overlaps with the left child's blocking activity.
//----------------------------------------------------------------------
const CostScalar rightChildLastRowElapsedTime =
rightChildCost->getCplr().getElapsedTime(rpp); // div-by-zero fix
// if rightChildLastRowElapsedTime is zero (for whatever reason ... this is
// suspicious ... ), set the ratio to be one -- don't divide by zero!
const CostScalar overlapRatio =
( rightChildLastRowElapsedTime.isZero() )
? csOne
: MINOF( csOne,
( ( leftChildCpbcTotalET * leftChildCpbcTotalNumProbes )
- ( rightChildCpbcTotalET * rightChildCpbcTotalNumProbes )
) / rightChildLastRowElapsedTime
);
//--------------------------------------------------------------
// Produce a vector which represents the portion of the right
// child's last row activity that does not overlap with the left
// child's blocking activity.
//--------------------------------------------------------------
SimpleCostVector nonOverlapRight = rightChildCost->getCplr();
nonOverlapRight.scaleByValue( csOne - overlapRatio );
//---------------------------------------------------------------------
// Multiply left child's blocking vector by its number of probes so it
// represents cumulative activity over all probes and is thus
// commensurate with last row activity.
//---------------------------------------------------------------------
SimpleCostVector leftBlockingAllProbes =
leftChildCost->getCpbcTotal() * leftChildCpbcTotalNumProbes;
//--------------------------------------------------------------------
// Produce a vector which represents the portion of the right child's
// last row activiity that overlaps with the left child's blocking
// activity. Reduce this vector to the extent by which it actually
// overlaps.
//--------------------------------------------------------------------
SimpleCostVector overlapRight = rightChildCost->getCplr();
overlapRight = overlapAdd(overlapRight.scaleByValue(overlapRatio),
leftBlockingAllProbes)
- leftBlockingAllProbes;
//-----------------------------------------------------------------------
// Since both children of a UNION act independently, merge the last row
// vectors of both children using overlapped addition. A portion of the
// right child's last row activity has been reduced to the extent that it
// can overlap with the left child's blocking activity.
//-----------------------------------------------------------------------
mergedCost->cplr() = overlapAdd(leftChildCost->getCplr(),
nonOverlapRight + overlapRight);
}
//-----------------------------------------------------------------
// Ensure that no component of merged first row vector exceeds the
// corresponding component of merged last row vector.
//-----------------------------------------------------------------
mergedCost->cpfr().enforceUpperBound(mergedCost->cplr());
//--------------------------------------------------------------------------
// Since both children of a UNION act independently, merge children's total
// blocking vectors using overlapped addition.
//--------------------------------------------------------------------------
mergedCost->cpbcTotal()
= overlapAdd( leftChildCost->getCpbcTotal(),
rightChildCost->getCpbcTotal() );
//---------------------------------------------------------------------------
// For blocking vectors, we use the same basic formula: the cost of the
// faster child plus an interferance factor. The quantity etMINOF(...)
// represents the cost of the faster child. The quantity vecMINOF(...)
// represents the interferance factor.
//---------------------------------------------------------------------------
mergedCost->cpbc1() = overlapAdd( etMINOF( leftChildCost->getCpbc1(),
rightChildCost->getCpbc1(),
rpp ),
vecMINOF( leftChildCost->getCpbc1(),
rightChildCost->getCpbc1() ) );
//-------------------------------------------------------------------------
// Assuming that this UNION operator's process and its child processes all
// run in separate CPUs, no interferance occurs, so the first row produced
// by the UNION depends on the fastest of the two children and the last
// row produced by the UNION depends on the slowest of the two children.
//-------------------------------------------------------------------------
//jo mergedCost->opfr() = etMINOF( leftChildCost->getOpfr(),
//jo rightChildCost->getOpfr(),
//jo rpp );
//jo mergedCost->oplr() = etMAXOF( leftChildCost->getOplr(),
//jo rightChildCost->getOplr(),
//jo rpp );
return mergedCost;
} // CostMethodMergeUnion::mergeBothLegsBlocking
//<pb>
// ----QUICKSEARCH FOR ROOT...............................................
/**********************************************************************/
/* */
/* CostMethodRelRoot */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodRelRoot::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodRelRoot::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
// -------------------------------------------------------------
// Save off estimated degree of parallelism. Always 1 for root.
// -------------------------------------------------------------
countOfStreams = 1;
// ---------------------------------------------------------------------
// A RelRoot performs no actual functions on the rows it get than just
// copying them to the application. The operator receives one stream of
// rows. In parallel plans, the Exchange below RelRoot collects rows
// from all streams, and send them to one single instance of RelRoot.
// ---------------------------------------------------------------------
CostScalar cpuCopyRow =
CostPrimitives::cpuCostForCopyRow(myVis().getRowLength());
CostScalar cpuFR = cpuCopyRow;
CostScalar cpuLR = cpuCopyRow * myRowCount_;
CostScalar readMetadataOpenFirstPartitionCpu=
CostPrimitives::getBasicCostFactor(CPUCOST_SUBSET_OPEN)*
op->getGroupAttr()->getNumBaseTables()*
CostPrimitives::getBasicCostFactor(MSCF_ET_CPU);
//fudge factor for cputime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// ---------------------------------------------------------------------
// Synthesize the cost vectors.
// ---------------------------------------------------------------------
SimpleCostVector cvFR (
cpuFR * ff_cpu,
csZero,
csZero,
readMetadataOpenFirstPartitionCpu,
csOne
);
SimpleCostVector cvLR (
cpuLR * ff_cpu,
csZero,
csZero,
readMetadataOpenFirstPartitionCpu,
csOne
);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"RELROOT::computeOperatorCost()\n");
fprintf(pfp,"childRowCount=%g\n",myRowCount_.value());
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return the cost object.
// ---------------------------------------------------------------------
Cost *costPtr = new STMTHEAP Cost (&cvFR,&cvLR,NULL,1,1);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodRelRoot::computeOperatorCostInternal().
//<pb>
// ----QUICKSEARCH FOR TUPLE..............................................
/**********************************************************************/
/* */
/* CostMethodTuple */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodTuple::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodTuple::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
Tuple* tp = (Tuple*) op;
// ---------------------------------------------------------------------
// Cost includes allocating the tuple and then evaluating the column
// expressions in the tuple.
// ---------------------------------------------------------------------
CostScalar cpuCostEvalExpr = csZero;
for(Lng32 i = 0; i < Lng32(tp->tupleExpr().entries()); i++)
cpuCostEvalExpr +=
CostPrimitives::cpuCostForEvalExpr(tp->tupleExpr()[i]);
CostScalar cpuFR = cpuCostAllocateTuple_ + cpuCostEvalExpr;
// ---------------------------------------------------------------------
// The Tuple operator returns exactly one row for each probe it gets.
// Thus, its total row count should just be probeCount.
// ---------------------------------------------------------------------
CostScalar cpuLR = cpuFR * noOfProbesPerStream_;
// Tuple operator process number of tuples in the IN list, and selectivity
// is 50% if there is a predicate otherwise it is a cross product.
// So, the cost should reflect this work otherwise we get many NJ plans
// with tupleList on the RHS. If TupleList is under NJ and COMP_INT_80 = 1,
// include total rowcount in the cost. Part of the fix is also in HGBY cost.
// Current value of myRowCount_ = numTuples * probes, so we
// divide this value by countOfStreams to compute the cost per ESP.
// If COMP_INT_80 = 0 means fix is OFF.
// > 0 means fix is ON. Default value is 3, so fix is ON.
Lng32 compInt80 = (ActiveSchemaDB()->getDefaults()).getAsLong(COMP_INT_80);
if ( (compInt80 > 0 ) AND isUnderNestedJoin_)
{
cpuLR = cpuFR * ((myRowCount_ / countOfStreams).minCsOne());
// scale up by CPUCOST_NJ_TUPLST_FF to avoid TupList under NJ.
cpuLR = cpuLR * CostPrimitives::getBasicCostFactor(CPUCOST_NJ_TUPLST_FF);
}
//fudge factor for cputime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// ---------------------------------------------------------------------
// Synthesize the cost vectors.
// ---------------------------------------------------------------------
const SimpleCostVector cvFR (
cpuFR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
const SimpleCostVector cvLR (
cpuLR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"TUPLE::computeOperatorCost()\n");
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP Cost (&cvFR,&cvLR,NULL,cpuCount,fragmentsPerCPU);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodTuple::computeOperatorCostInternal().
//<pb>
/**********************************************************************/
/* */
/* CostMethodTranspose */
/* */
/**********************************************************************/
// Compute common costing parameters.
//
void
CostMethodTranspose::cacheParameters(RelExpr *op,
const Context *myContext)
{
// Just to make sure things are working as expected
//
CMPASSERT(op->getOperatorType() == REL_TRANSPOSE);
CostMethod::cacheParameters(op,myContext);
// We know at this point that the op is a Physical Transpose node.
//
PhysTranspose *transpose = (PhysTranspose *)op;
// The set of values that the transpose operator
// will move for to produce one row.
//
ValueIdSet moveValues;
for(CollIndex v = 0; v < transpose->transUnionVectorSize(); v++) {
const ValueIdList &valIdList = transpose->transUnionVector()[v];
for(CollIndex vidu = 0; vidu < valIdList.entries(); vidu++) {
const ValueIdUnion *valIdUnion =
(ValueIdUnion *)valIdList[vidu].getValueDesc()->getItemExpr();
moveValues += valIdUnion->getSource(0);
}
}
// The estimated cost to produce one row.
//
CostScalar cpuCostToProduceOneRow =
CostPrimitives::cpuCostForCopySet(moveValues) +
CostPrimitives::getBasicCostFactor(EX_OP_ALLOCATE_TUPLE);
// Estimated cost to produce all rows.
//
cpuCostToProduceAllRows_ = myRowCount_ * cpuCostToProduceOneRow;
}
// CostMethodTranspose::computeOperatorCostInternal() -------------------------
// Compute the cost of this Transpose node given the optimization context.
//
// Parameters
//
// RelExpr *op
// IN - The PhysTranpose node which is being costed.
//
// Context *myContext
// IN - The optimization context within which to cost this node.
//
// long& countOfStreams
// OUT - Estimated degree of parallelism for returned preliminary cost.
//
Cost *
CostMethodTranspose::computeOperatorCostInternal(RelExpr *op,
const Context *myContext,
Lng32& countOfStreams)
{
// Just to make sure things are working as expected
//
CMPASSERT(op->getOperatorType() == REL_TRANSPOSE);
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
CostScalar cpuCostToProduceLastRow =
cpuCostToProduceAllRows_ / countOfStreams_;
CostScalar cpuCostToProduceFirstRow =
cpuCostToProduceLastRow / myRowCount_ / noOfProbes_;
//fudge factor for cputime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
const SimpleCostVector cvFirstRow(
cpuCostToProduceFirstRow * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
const SimpleCostVector cvLastRow(
cpuCostToProduceLastRow * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
return new STMTHEAP
Cost(&cvFirstRow, &cvLastRow, NULL, cpuCount, fragmentsPerCPU);
} // CostMethodTranspose::computeOperatorCostInternal()
/**********************************************************************/
/* */
/* CostMethodCompoundStmt */
/* */
/**********************************************************************/
// Compute common costing parameters.
//
void
CostMethodCompoundStmt::cacheParameters(RelExpr *op,
const Context *myContext)
{
// Just to make sure things are working as expected
//
CMPASSERT(op->getOperatorType() == REL_COMPOUND_STMT);
CostMethod::cacheParameters(op,myContext);
// The estimated cost to produce one row.
// It is treated as a constant.
CostScalar cpuCostToProduceOneRow =
CostPrimitives::getBasicCostFactor(EX_OP_ALLOCATE_TUPLE);
cpuCostToProduceAllRows_ = myRowCount_ * cpuCostToProduceOneRow;
} // CostMethodCompoundStmt::cacheParameters()
//-------------------------------------------------------------------------
// CostMethodCompoundStmt::computeOperatorCostInternal()
// Compute the cost of this Compound Statement node, given the optimization context.
//
// Parameters
//
// RelExpr *op
// IN - The PhysTranpose node which is being costed.
//
// Context *myContext
// IN - The optimization context within which to cost this node.
//-------------------------------------------------------------------------
Cost *
CostMethodCompoundStmt::computeOperatorCostInternal(RelExpr *op,
const Context *myContext,
Lng32& countOfStreams)
{
// Just to make sure things are working as expected
//
CMPASSERT(op->getOperatorType() == REL_COMPOUND_STMT);
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
countOfStreams = countOfStreams_;
CostScalar cpuLR =
cpuCostToProduceAllRows_ / countOfStreams_;
CostScalar cpuFR =
cpuLR / myRowCount_ / noOfProbes_;
//fudge factor for cputime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
const SimpleCostVector cvFirstRow(
cpuFR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
const SimpleCostVector cvLastRow(
cpuLR * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
return new STMTHEAP
Cost(&cvFirstRow, &cvLastRow, NULL, cpuCount, fragmentsPerCPU);
} // CostMethodTranspose::computePreliminaryCost()
/**********************************************************************/
/* */
/* CostMethodStoredProc */
/* */
/**********************************************************************/
// CostMethodStoredProc::computeOperatorCostInternal() -----------------------
// Compute the cost of this Stored Procedure node given the optimization
// context.
//
//
// Parameters
//
// RelExpr *op
// IN - The PhysTranpose node which is being costed.
//
// Context *myContext
// IN - The optimization context within which to cost this node.
//
// long& countOfStreams
// OUT - Estimated degree of parallelism for returned preliminary cost.
//
Cost *
CostMethodStoredProc::computeOperatorCostInternal(RelExpr *op,
const Context *myContext,
Lng32& countOfStreams)
{
// Completely ficticious cost for now of 1000 CPU instructions and
// no I/O or message cost.
//
const SimpleCostVector cv (
csOne * CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions(),
csZero,
csZero,
csZero,
csOne
);
// Stored procedures never run in parallel.
//
countOfStreams = 1;
return new STMTHEAP Cost( &cv, &cv, NULL, 1, 1 );
} // CostMethodStoredProc::computeOperatorCostInternal()
/**********************************************************************/
/* */
/* CostMethodTableMappingUDF */
/* */
/**********************************************************************/
// CostMethodTableMappingUDF::computeOperatorCostInternal() ----------
// Compute the cost of this Stored Procedure node given the optimization
// context.
//
//
// Parameters
//
// RelExpr *op
// IN - The TableMappingUDF node which is being costed.
//
// Context *myContext
// IN - The optimization context within which to cost this node.
//
// long& countOfStreams
// OUT - Estimated degree of parallelism for returned preliminary cost.
//
Cost *
CostMethodTableMappingUDF::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
CostScalar cpuTimeForFirstRow;
CostScalar cpuTimeForLastRow;
EstLogPropSharedPtr inputLP = myContext->getInputLogProp();
EstLogPropSharedPtr outputLP = op->getGroupAttr()->outputLogProp(inputLP);
NADefaults &defs = ActiveSchemaDB()->getDefaults();
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// Save off estimated degree of parallelism.
countOfStreams = countOfStreams_;
/**** May not be needed for now *****************
// can mapReduceOp be BMO?
// add CQD which tells complexity of UDF.
// MAPREDUCE_UDR_COMPLEXITY : 1 means Small, 2 means Medium, 3 means Large
CostScalar mapRedUdfComplxity = defs.getAsLong(MAPREDUCE_UDR_COMPLEXITY);
CostScalar costAdj = 1;
if (mapRedUdfComplxity == 2)
costAdj = 5; // multiply total cost 5 times for medium UDF
else if (mapRedUdfComplxity == 3)
costAdj = 10; // multiply total cost 10 times for Large UDF
**************************************************/
// Size of a record in kilobytes. Let's not use this one for now. Enable it if required.
//double recordSize = op->child(0).getGroupAttr()->getRecordLength() / 1024.0;
// per stream Rows from child
CostScalar rowsFromChildPerStream ;
if (op->getArity() == 1)
{
EstLogPropSharedPtr childOutputLP = op->child(0).outputLogProp( inputLP );
rowsFromChildPerStream = childOutputLP->getResultCardinality() / countOfStreams;
}
else
{
// when TMUDF is the leaf node we do not know its cardinality
// (till a discovery method is added). Till then use a value
// that will encourage parallel plan creation.
rowsFromChildPerStream = 100000/ countOfStreams;
}
// get cpu FF
const double ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
const double udfCpuCost = defs.getAsDouble(COMP_FLOAT_9) ; // assumption, will get overridden by dll
// Add default cost: cost_per_byte * num_rows * cpu_ff.
// First row cost includes cost for 1 row.
cpuTimeForFirstRow = udfCpuCost * ff_cpu;
// cpuTimeForFirstRow *= costAdj;
// For last row, all probes coming from above must be included as well:
cpuTimeForLastRow = udfCpuCost*ff_cpu;
cpuTimeForLastRow *= rowsFromChildPerStream*noOfProbesPerStream_ ;
// cpuTimeForLastRow *= costAdj;
SimpleCostVector cvFR(
cpuTimeForFirstRow,
csZero, // no IO time
csZero, // no message time
csZero, // no idle time
noOfProbesPerStream_); // num. of probes
SimpleCostVector cvLR(
cpuTimeForLastRow,
csZero, // no IO time
csZero, // no message time
csZero, // no idle usage
noOfProbesPerStream_);
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
return new STMTHEAP Cost( &cvFR,
&cvLR,
NULL,
countOfStreams_,
fragmentsPerCPU
);
}
/**********************************************************************/
/* */
/* CostMethodFastExtract */
/* */
/**********************************************************************/
// CostMethodFastExtract::computeOperatorCostInternal() ----------
// Compute the cost of this Fast Extract node given the optimization
// context.
//
//
// Parameters
//
// RelExpr *op
// IN - The FastExtract node which is being costed.
//
// Context *myContext
// IN - The optimization context within which to cost this node.
//
// long& countOfStreams
// OUT - Estimated degree of parallelism for returned preliminary cost.
//
Cost *
CostMethodFastExtract::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
CostScalar cpuTimeForFirstRow;
CostScalar cpuTimeForLastRow;
EstLogPropSharedPtr inputLP = myContext->getInputLogProp();
EstLogPropSharedPtr outputLP = op->getGroupAttr()->outputLogProp(inputLP);
NADefaults &defs = ActiveSchemaDB()->getDefaults();
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// Save off estimated degree of parallelism.
countOfStreams = countOfStreams_;
// per stream Rows from child
CostScalar rowsFromChildPerStream ;
EstLogPropSharedPtr childOutputLP = op->child(0).outputLogProp( inputLP );
rowsFromChildPerStream = childOutputLP->getResultCardinality() / countOfStreams;
// get cpu FF
const double ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
// First row cost includes cost for 1 row.
cpuTimeForFirstRow = csOne * ff_cpu;
// For last row, all probes coming from above must be included as well:
cpuTimeForLastRow = csOne*ff_cpu;
cpuTimeForLastRow *= rowsFromChildPerStream*noOfProbesPerStream_ ;
SimpleCostVector cvFR(
cpuTimeForFirstRow,
csZero, // no IO time
csZero, // no message time
csZero, // no idle time
noOfProbesPerStream_); // num. of probes
SimpleCostVector cvLR(
cpuTimeForLastRow,
csZero, // no IO time
csZero, // no message time
csZero, // no idle usage
noOfProbesPerStream_);
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
return new STMTHEAP Cost( &cvFR,
&cvLR,
NULL,
countOfStreams_,
fragmentsPerCPU
);
}
/**********************************************************************/
// Begin cost methods for WRITE operations
/**********************************************************************/
//<pb>
// ----QUICKSEARCH FOR HbaseInsert........................................
/**********************************************************************/
/* */
/* CostMethodHbaseInsert */
/* */
/**********************************************************************/
// -----------------------------------------------------------------------
// CostMethodHbaseInsert::cacheParameters()
// -----------------------------------------------------------------------
void CostMethodHbaseInsert::cacheParameters(RelExpr* op, const Context * myContext)
{
CostMethod::cacheParameters(op, myContext);
HbaseInsert* insOp = (HbaseInsert *)op;
CMPASSERT(partFunc_ != NULL);
NodeMap * nodeMap = (NodeMap *)partFunc_->getNodeMap();
if (nodeMap)
activePartitions_ = (CostScalar)nodeMap->getNumActivePartitions();
else
// Occasionally (e.g., regress/fullstack2/test023, the insert/select
// from t023t1 into t023t2 using a transpose operator), we get
// a ReplicateNoBroadcastPartitioningFunction lacking a node map.
// In this case we'll just use the number of partitions from the
// partitioning function itself -- which is probably an ESP count.
activePartitions_ = (CostScalar)partFunc_->getCountOfPartitions();
// The number of asynchronous streams is USUALLY the # of active parts.
countOfAsynchronousStreams_ = activePartitions_;
} // CostMethodHbaseInsert::cacheParameters()
// -----------------------------------------------------------------------
// CostMethodHbaseInsert::computeOperatorCostInternal()
// -----------------------------------------------------------------------
Cost* CostMethodHbaseInsert::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
cacheParameters(op, myContext);
estimateDegreeOfParallelism();
// ------------------------------------------------------
// Save off our current estimated degree of parallelism.
// in the 'out' parameter; we might revise it below
// ------------------------------------------------------
countOfStreams = countOfStreams_;
CostScalar currentCpus =
(CostScalar)myContext->getPlan()->getPhysicalProperty()->getCurrentCountOfCPUs();
activeCpus_ = MINOF(countOfAsynchronousStreams_, currentCpus);
// update count of streams; the caller of the method uses this value
if ((countOfAsynchronousStreams_ > 0) &&
(countOfAsynchronousStreams_ < countOfStreams)
)
countOfStreams = (Lng32)countOfAsynchronousStreams_.getValue();
streamsPerCpu_ =
(countOfAsynchronousStreams_ / activeCpus_).getCeiling();
CostScalar noOfProbesPerStream(csOne);
// Determine the number of probes per stream. Use this number as
// the number of rows to insert (this is "per-stream" costing).
noOfProbesPerStream =
(noOfProbes_ / countOfAsynchronousStreams_).minCsOne();
// ************************************************************
// Compute the write/read cost for the insert
//
// ************************************************************
// ---------------------------------------------------------------------
// Synthesize the cost vectors.
// ---------------------------------------------------------------------
SimpleCostVector cvFR;
SimpleCostVector cvLR;
// For now, we don't bother to estimate CPU time, I/O time, transfer
// time or idle time, since we really are only supporting the new
// cost model.
//
// Future possible improvements:
//
// 1. Take into account HBase memstore insertion cost. The memstore
// uses a Red-Black tree which has o(n * log(n)) insertion cost. To
// model this correctly, we'd need to take into account the number
// of HBase regions rather than the number of ESPs, that is, to
// divide the number of probes by the number of HBase regions to find
// n. This cost will be paid no matter how many inserting streams
// there are so by itself this may not be interesting. It would only
// be interesting if there were a choice in the plan between inserting
// and not inserting (e.g. if we were considering bypassing the
// memstore, or if we were considering storing an intermediate result,
// neither of which are choices we examine today).
//
// 2. Take into account whether the probes are in key order. There
// is anecdotal evidence that if the probes are in key order, then
// memstore insertion cost is less. Possibly this is true only if
// inserting at the end or the beginning of the key range in a
// partition; intuitively inserting in the middle would seem to incur
// the full insertion cost. This is worthwhile taking into
// consideration as it opens the possibility of choosing between a
// plan that sorts rows in Trafodion before passing them to HBase
// vs. a plan that does not. To make this calculation we must know
// the memstore insertion cost (point 1 above), the order of the
// probes, whether the ESPs are aligned to the Regions of the
// target table, and whether we are inserting at the beginning or
// end of the key range. A first approximation to the last item
// would be whether the target table is empty. This is interesting
// because the case that we are doing an INSERT/SELECT into a new
// table is likely to be common.
//
// 3. Take into account memstore flush cost. We could add I/O time
// for flushes. For example, we could compare the number of probes
// per HBase Region with the number of rows that would cause a
// flush (the latter can be obtained from
// HbaseClient::estimateMemStoreRows() and is a function of the
// HBase parameter hbase.hregion.memstore.flush.size). Again, this
// cost will be paid no matter the plan choice so this is not
// interesting today. As with point 1, it becomes interesting only
// if there is a plan choice between inserting via memstore or not.
//
// In the interest of time, we move forward without these
// improvements for now.
cvFR.setNumProbes(noOfProbesPerStream);
cvLR.setNumProbes(noOfProbesPerStream);
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
Cost *costPtr = new STMTHEAP
Cost(&cvFR
, &cvLR
, NULL
, Lng32(activeCpus_.getValue())
, Lng32(streamsPerCpu_.getValue())
);
#ifndef NDEBUG
if (CmpCommon::getDefault(OPTIMIZER_PRINT_COST) == DF_ON)
{
pfp = stdout;
fprintf(pfp, "HbaseInsert elapsed time: ");
fprintf(pfp, "%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp, "\n");
}
#endif
return costPtr;
} // CostMethodHbaseInsert::computeOperatorCostInternal
// -----------------------------------------------------------------------
// CostMethodHbaseInsert::cleanUp()
//
// The method cleans up cached parameters which need deallocation and
// should be called after a costing session is done.
// -----------------------------------------------------------------------
void CostMethodHbaseInsert::cleanUp()
{
activePartitions_ = csOne;
activeCpus_ = csOne;
streamsPerCpu_ = csOne;
countOfAsynchronousStreams_ = csOne;
// Clean up fields in base class
CostMethod::cleanUp();
} // CostMethodHbaseInsert::cleanUp().
//<pb>
/**********************************************************************/
/* */
/* CostMethodUnPackRows */
/* */
/**********************************************************************/
// Compute common costing parameters.
//
void
CostMethodUnPackRows::cacheParameters(RelExpr *op,
const Context *myContext)
{
// Just to make sure things are working as expected
//
CMPASSERT(op->getOperatorType() == REL_UNPACKROWS);
CostMethod::cacheParameters(op,myContext);
// We know at this point that the op is a Physical UnPackRows node.
//
PhysUnPackRows *unPackRows = (PhysUnPackRows *)op;
// The set of values that the unPackRows operator
// will move for to produce one row.
//
ValueIdSet moveValues;
moveValues.insertList(unPackRows->unPackExpr());
// The estimated cost to produce one row.
//
CostScalar cpuCostToProduceOneRow =
CostPrimitives::cpuCostForCopySet(moveValues) +
CostPrimitives::getBasicCostFactor(EX_OP_ALLOCATE_TUPLE);
// Estimated cost to produce all rows.
//
cpuCostToProduceAllRows_ = myRowCount_ * cpuCostToProduceOneRow;
}
// CostMethodUnPackRows::computeOperatorCostInternal() -------------------------
// Compute the cost of this UnPackRows node given the optimization context.
//
// Parameters
//
// RelExpr *op
// IN - The PhysUnPackRows node which is being costed.
//
// Context *myContext
// IN - The optimization context within which to cost this node.
//
// long& countOfStreams
// OUT - Estimated degree of parallelism for returned preliminary cost.
//
Cost *
CostMethodUnPackRows::computeOperatorCostInternal(RelExpr *op,
const Context *myContext,
Lng32& countOfStreams)
{
// Just to make sure things are working as expected
//
CMPASSERT(op->getOperatorType() == REL_UNPACKROWS);
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
CostScalar cpuCostToProduceLastRow =
cpuCostToProduceAllRows_ / countOfStreams_;
CostScalar cpuCostToProduceFirstRow =
cpuCostToProduceLastRow / myRowCount_ / noOfProbes_;
//fudge factor for cpuTime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
const SimpleCostVector cvFirstRow(
cpuCostToProduceFirstRow * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
const SimpleCostVector cvLastRow(
cpuCostToProduceLastRow * ff_cpu,
csZero,
csZero,
csZero,
noOfProbesPerStream_
);
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
return new STMTHEAP
Cost(&cvFirstRow, &cvLastRow, NULL, cpuCount, fragmentsPerCPU);
} // CostMethodUnPackRows::computeOperatorCostInternal()
/**********************************************************************/
/* */
/* CostMethodRelSequence */
/* */
/**********************************************************************/
// Compute common costing parameters.
//
void
CostMethodRelSequence::cacheParameters(RelExpr *op,
const Context *myContext)
{
// Just to make sure things are working as expected
//
DCMPASSERT(op->getOperatorType() == REL_SEQUENCE);
CostMethod::cacheParameters(op,myContext);
// We know at this point that the op is a Physical RelSequence node.
//
const PhysSequence *relSequence = (PhysSequence *)op;
// The set of values that the RelSequence operator
// will move to produce one row.
historyBufferWidthInBytes_ = relSequence->getEstHistoryRowLength(); //historyIds.getRowLength();
const Lng32 numHistoryRows = MIN_ONE(relSequence->numHistoryRows());
historyBufferSizeInBytes_ = numHistoryRows * historyBufferWidthInBytes_;
// The estimated cost to produce one row.
//
CostScalar cpuCostToProduceOneRow =
// The cost to compute the sequence functions.
CostPrimitives::cpuCostForCopyRow(relSequence->
sequenceFunctions().getRowLength()) +
// The cost to copy history buffer row to result.
CostPrimitives::cpuCostForCopyRow(historyBufferWidthInBytes_) +
// The cost to allocate one tuple per row.
CostPrimitives::getBasicCostFactor(EX_OP_ALLOCATE_TUPLE);
// Estimated cost to produce all rows.
//
cpuCostToProduceAllRows_ = myRowCount_ * cpuCostToProduceOneRow;
}
// CostMethodRelSequence::computeOperatorCostInternal() -------------------------
// Compute the cost of this RelSequence node given the optimization context.
//
// Parameters
//
// RelExpr *op
// IN - The PhysSequence node which is being costed.
//
// Context *myContext
// IN - The optimization context within which to cost this node.
//
// long& countOfStreams
// OUT - Estimated degree of parallelism for returned preliminary cost.
//
Cost *
CostMethodRelSequence::computeOperatorCostInternal(RelExpr *op,
const Context *myContext,
Lng32& countOfStreams)
{
// Just to make sure things are working as expected
//
DCMPASSERT(op->getOperatorType() == REL_SEQUENCE);
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// ---------------------------------------------------------------------
// Cost scalars to be computed.
// ---------------------------------------------------------------------
CostScalar memFRInKB = historyBufferSizeInBytes_ / 1024.;
CostScalar memLRInKB = memFRInKB;
CostScalar cpuLR = cpuCostToProduceAllRows_ / countOfStreams_;
CostScalar cpuFR = (cpuLR / myRowCount_) / noOfProbes_;
CostScalar seekFR;
CostScalar transferFRInKB;
CostScalar seekLR;
CostScalar transferLRInKB;
// Now, consider the possibility of page faults.
if (isBMO_ AND memFRInKB > csZero AND memFRInKB > memoryLimit_) {
double pageSizeInKB =
CostPrimitives::getBasicCostFactor(DEF_PAGE_SIZE);
// Assume there is always one page fault to get first row.
//
seekFR = csOne;
transferFRInKB = seekFR * pageSizeInKB;
Lng32 numHistoryRowsPerPage =
(Lng32)((pageSizeInKB * 1024)) / historyBufferWidthInBytes_;
// Since the history buffer does not fit in memory and since it is
// accessed in a circular fashion, there will be a page fault for
// every page of rows added to the history buffer.
//
seekLR = myRowCount_ / numHistoryRowsPerPage;
// Also, there is a chance that the evaluation of the sequence
// functions will access a row of the history buffer which is not
// in memory causing more page faults.
//
// This is the probability of page faults assuming random access to
// the history buffer.
//
CostScalar probOfPageFaults = (memFRInKB - memoryLimit_) / memFRInKB;
// But the access will tend to be local, not random, so adjust the
// probability.
//
probOfPageFaults = probOfPageFaults * probOfPageFaults;
seekLR += myRowCount_ * probOfPageFaults;
transferLRInKB = seekLR * pageSizeInKB;
seekLR = seekLR / countOfStreams_;
transferLRInKB = transferLRInKB / countOfStreams_;
}
//fudge factor for cpuTime, ioSeeks & ioTransfer
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
const CostScalar ff_seeks = CURRSTMT_OPTDEFAULTS->getTimePerSeek();
const CostScalar ff_seqIO = CURRSTMT_OPTDEFAULTS->getTimePerSeqKb();
// CPUTime, IOTime= SeekTime + Transfer Time, no messages, never idle
// num of probes are the five parameters passed.
const SimpleCostVector
cvFR(cpuFR * ff_cpu, // CPU Time.
seekFR * ff_seeks + transferFRInKB * ff_seqIO, // IOTime
csZero, // no messages
csZero, // never Idle
noOfProbesPerStream_); // num probes
const SimpleCostVector
cvLR(cpuLR * ff_cpu, // CPU Time.
seekLR * ff_seeks + transferLRInKB * ff_seqIO, // IOTime.
csZero, // no messages
csZero, // never Idle
noOfProbesPerStream_); // num probes
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
return new STMTHEAP Cost(&cvFR, &cvLR, NULL, cpuCount, fragmentsPerCPU);
} // CostMethodRelSequence::computeOperatorCostInternal()
/**********************************************************************/
/* */
/* CostMethodSample */
/* */
/**********************************************************************/
// Compute common costing parameters.
//
// CostMethodSample::computeOperatorCostInternal() ---------------------
// Compute the cost of this Sample node given the optimization context.
//
// Parameters
//
// RelExpr *op
// IN - The PhysSample node which is being costed.
//
// Context *myContext
// IN - The optimization context within which to cost this node.
//
// long& countOfStreams
// OUT - Estimated degree of parallelism for returned preliminary cost.
//
Cost *
CostMethodSample::computeOperatorCostInternal(RelExpr *op,
const Context *myContext,
Lng32& countOfStreams)
{
// Just to make sure things are working as expected
//
DCMPASSERT(op->getOperatorType() == REL_SAMPLE);
// We know at this point that the op is a Physical RelSequence node.
//
PhysSample *relSample = (PhysSample *)op;
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
Lng32 numBalanceExpr = 0;
DCMPASSERT(relSample->balanceExpr().entries() == 1);
ValueId balanceRoot;
relSample->balanceExpr().getFirst(balanceRoot);
ItemExpr *balExpr = (ItmBalance *)balanceRoot.getItemExpr();
while(balExpr) {
numBalanceExpr++;
balExpr = balExpr->child(2);
}
CostScalar cpuCostToProcessOneRow = numBalanceExpr *
(CostPrimitives::getBasicCostFactor(CPUCOST_EVAL_SIMPLE_PREDICATE) +
(2 * CostPrimitives::getBasicCostFactor(CPUCOST_EVAL_ARITH_OP)));
EstLogPropSharedPtr inputLP = myContext->getInputLogProp();
EstLogPropSharedPtr childOutputLP = op->child(0).outputLogProp( inputLP );
const CostScalar & childNumRows = childOutputLP->getResultCardinality();
cpuCostToProduceAllRows_ = cpuCostToProcessOneRow * childNumRows;
CostScalar cpuLR = cpuCostToProduceAllRows_ / countOfStreams_;
CostScalar cpuFR = (cpuLR / myRowCount_) / noOfProbes_;
//fudge factor for cpuTime
const CostScalar ff_cpu = CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions();
const SimpleCostVector
cvFR(cpuFR * ff_cpu, // CPU Time
csZero, // IO Time
csZero, // no messages
csZero, // never idle
noOfProbesPerStream_); // num probes
const SimpleCostVector
cvLR(cpuLR * ff_cpu, // CPU Time
csZero, // IO Time
csZero, // no messages
csZero, // never idle
noOfProbesPerStream_); // num probes
// ---------------------------------------------------------------------
// Synthesize and return cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
return new STMTHEAP
Cost(&cvFR, &cvLR, NULL, cpuCount, fragmentsPerCPU);
} // CostMethodSample::computeOperatorCostInternal()
// -----------------------------------------------------------------------
// CostMethodIsolatedScalarUDF::computeOperatorCostInternal().
// -----------------------------------------------------------------------
Cost*
CostMethodIsolatedScalarUDF::computeOperatorCostInternal(RelExpr* op,
const Context* myContext,
Lng32& countOfStreams)
{
// ---------------------------------------------------------------------
// Preparatory work.
// ---------------------------------------------------------------------
cacheParameters(op,myContext);
estimateDegreeOfParallelism();
// -----------------------------------------
// Save off estimated degree of parallelism.
// -----------------------------------------
countOfStreams = countOfStreams_;
// -----------------------------------------
// Determine the number of input Rows/Probes
// -----------------------------------------
CostScalar noOfProbes =
( myContext->getInputLogProp()->getResultCardinality() ).minCsOne();
// The noOfProbes is used to scale up the cost of the UDF.
// However since the UDF assumes the cost may be different for the first
// time it is called due to initialization of the UDF's data structures,
// perhaps loading of DLLs etc, we will subtract the first probe out.
noOfProbes -= csOne;
IsolatedScalarUDF *udf = (IsolatedScalarUDF *) op;
// Make sure we actually are a UDF.
CMPASSERT( udf != NULL );
CMPASSERT( op->getOperatorType() == REL_ISOLATED_SCALAR_UDF );
// ---------------------------------------------------------------------
// This CostMethod basically computes a CPU cost for IsolatedScalarUDF
// operators.
//
// It uses the intialCost numbers for the first probe as it is assumed
// that it may require initialization of the routine's data structures,
// may include loading of DLLs
// It applies the formula:
//
// cpu = initialCpuCost * fanOut +
// normalCpuCost * noOfProbes * fanOut+
//
// where the row counts are those
// of the total result set, and then amortize the cost across streams.
// It takes the first row count to be just its last row count amortized
// across the no of probes.
// We factor in the cost of sending messages to and from the UDR
// server in a similar fashion:
//
// msgs = initialMsgCost * 2 * fanOut +
// normalMsgCost * noOfProbes * 2 * fanOut (2 messages per row)
// ---------------------------------------------------------------------
// Make sure we have a RoutineDesc.
CMPASSERT( udf->getRoutineDesc() != NULL );
// Get a reference to the routine Cost Vectors.
SimpleCostVector &initialCostV = udf->getRoutineDesc()->getEffInitialRowCostVector();
SimpleCostVector &normalCostV = udf->getRoutineDesc()->getEffNormalRowCostVector();
// Gather the different cost numbers
CostScalar initialCpuCost = initialCostV.getCPUTime();
CostScalar initialMsgCost = initialCostV.getMessageTime();
CostScalar initialIOCost = initialCostV.getIOTime();
CostScalar normalCpuCost = normalCostV.getCPUTime();
CostScalar normalMsgCost = normalCostV.getMessageTime();
CostScalar normalIOCost = normalCostV.getIOTime();
CostScalar fanOut = udf->getRoutineDesc()->getEffFanOut();
// Following code copied from FileScan
CostScalar resultSetCardinality =
udf->getGroupAttr()->
outputLogProp(myContext->getInputLogProp())->getResultCardinality();
// $$$ Due to a bug in histograms (up to tag A091197_1)
// $$$ sometimes the cardinality is negative, if so, fix it
// $$$ to pass regressions:
if ( resultSetCardinality.isLessThanZero() /* < csZero */ )
resultSetCardinality = CostScalar(fanOut);
CostScalar cpu = initialCpuCost + (normalCpuCost * (fanOut-1));
CostScalar msgs = initialMsgCost * 2 + (normalMsgCost * (fanOut-1));
CostScalar io = initialIOCost + (normalIOCost * (fanOut-1));
// ---------------------------------------------------------------------
// Synthesize the First Row cost vector.
// This is used for [Fist N] type queries..
// The number we are computing here is actually only accurate for
// [First 1], but it is consistent with what we do for FIXEDCOST nodes.
// ---------------------------------------------------------------------
SimpleCostVector cvFR (
cpu/countOfStreams_ // converting CPU instr
* CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions(), //into time
io/countOfStreams_,msgs/countOfStreams_,csZero,
noOfProbesPerStream_);
cpu += normalCpuCost * noOfProbes * fanOut;
msgs += normalMsgCost * noOfProbes * 2 * fanOut;
// double it to account for two messages per row.
// we are making a rough assumption here that we only require
// one message to the UDR server and one return message for the
// result per row.
io += normalIOCost * noOfProbes * fanOut;
// ---------------------------------------------------------------------
// Synthesize the stead state cost vector.
// This is used for [Last N] type queries..
// ---------------------------------------------------------------------
SimpleCostVector cvLR (
cpu/countOfStreams_ * CURRSTMT_OPTDEFAULTS->getTimePerCPUInstructions(),
io/countOfStreams_,msgs/countOfStreams_,csZero,
noOfProbesPerStream_);
// ---------------------------------------------------------------------
// For debugging.
// ---------------------------------------------------------------------
#ifndef NDEBUG
NABoolean printCost =
( CmpCommon::getDefault( OPTIMIZER_PRINT_COST ) == DF_ON );
if ( printCost )
{
pfp = stdout;
fprintf(pfp,"IsolatedScalarUDF::computeOperatorCost()\n");
cvFR.print(pfp);
cvLR.print(pfp);
}
#endif
// ---------------------------------------------------------------------
// Synthesize and return the cost object.
// ---------------------------------------------------------------------
// Find out the number of cpus and number of fragments per cpu.
Lng32 cpuCount, fragmentsPerCPU;
determineCpuCountAndFragmentsPerCpu( cpuCount, fragmentsPerCPU );
Cost *costPtr = new STMTHEAP Cost( &cvFR,
&cvLR,
NULL,
cpuCount,
fragmentsPerCPU
);
#ifndef NDEBUG
if ( printCost )
{
fprintf(pfp, "Elapsed time: ");
fprintf(pfp,"%f", costPtr->
convertToElapsedTime(
myContext->getReqdPhysicalProperty()).
value());
fprintf(pfp,"\n");
}
#endif
return costPtr;
} // CostMethodIsolatedScalarUDF::computeOperatorCostInternal().
//<pb>