/* ---- This file is part of SECONDO. Copyright (C) 2009, University in Hagen, Faculty of Mathematics and Computer Science, Database Systems for New Applications. SECONDO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. SECONDO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SECONDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ---- 1 Implementation File Sort.cpp June 2009, Sven Jungnickel. Initial version 2 Includes and defines */ #include #include "stdlib.h" #include "LogMsg.h" #include "QueryProcessor.h" #include "StandardTypes.h" #include "RTuple.h" #include "Sort.h" #define HLINE "---------------------------------------------------------------" /* 3 External linking */ extern QueryProcessor* qp; using namespace std; /* 4 Implementation of class ~SortedRunInfo~ */ namespace extrel2 { SortedRunInfo::SortedRunInfo(int no) : No(no) , RunLength(0) , RunSize(0) , MinimumRunLength(0) , AdditionalRunLength(0) , TuplesPassedToNextRun(0) , TuplesInMemory(0) , TuplesOnDisk(0) , RunRatio(0) { } SortedRunInfo::SortedRunInfo(extrel2::SortedRunInfo& info) { No = info.No; RunLength = info.RunLength; RunSize = info.RunSize; MinimumRunLength = info.MinimumRunLength; AdditionalRunLength = info.AdditionalRunLength; TuplesPassedToNextRun = info.TuplesPassedToNextRun; TuplesInMemory = info.TuplesInMemory; TuplesOnDisk = info.TuplesOnDisk; RunRatio = info.RunRatio; } SortedRunInfo::~SortedRunInfo() { } float SortedRunInfo::Ratio() { return (float)RunLength / (float)MinimumRunLength; } float SortedRunInfo::Size() { return (float)RunSize / (float)( 1024 * 1024 ); } ostream& operator<<(ostream& stream, SortedRunInfo& info) { stream << setw(2) << info.No << ": " << info.RunLength << " Tuples / " << info.Size() << " MB " << "(Minimum: " << info.MinimumRunLength << ", Added: " << info.AdditionalRunLength << ")" << " => Ratio: " << info.Ratio() << endl << " TuplesPassedToNextRun: " << info.TuplesPassedToNextRun << " TuplesInMemory: " << info.TuplesInMemory << " TuplesOnDisk: " << info.TuplesOnDisk << endl; return stream; } /* 4 Implementation of class ~SortInfo~ */ SortInfo::SortInfo(size_t bufferSize, size_t ioBufferSize) : BufferSize(bufferSize), IOBufferSize(ioBufferSize), MaxMergeFanIn(0), F0(0), W(0), IntermediateTuples(0), InitialRunsCount(0), TotalTupleCount(0), TotalTupleSize(0), MinTupleSize(INT_MAX), MaxTupleSize(0), AvgTupleSize(0), TotalComparisons(0), TimeRunPhase(""), TimeMergePhase(""), TimeTotal(""), RunStatistics(true) { } SortInfo::~SortInfo() { clearAll(); } SortedRunInfo* SortInfo::CurrentRun() { return this->InitialRunInfo[this->InitialRunsCount-1]; } void SortInfo::NewRun() { this->InitialRunsCount++; this->InitialRunInfo.push_back(new SortedRunInfo(this->InitialRunsCount)); } void SortInfo::clearAll() { BufferSize = 0; IOBufferSize = 0; MaxMergeFanIn = 0; F0 = 0; W = 0; IntermediateTuples = 0; InitialRunsCount = 0; TotalTupleCount = 0; TotalTupleSize = 0; MinTupleSize = INT_MAX; MaxTupleSize = 0; AvgTupleSize = 0; TotalComparisons = 0; TimeRunPhase = ""; TimeMergePhase = ""; TimeTotal = ""; vector::iterator iter; for( iter = InitialRunInfo.begin(); iter != InitialRunInfo.end(); iter++ ) { delete *iter; } InitialRunInfo.clear(); for( iter = FinalRunInfo.begin(); iter != FinalRunInfo.end(); iter++ ) { delete *iter; } FinalRunInfo.clear(); } void SortInfo::UpdateStatistics(size_t s) { int n = (int)s; this->TotalTupleCount++; this->TotalTupleSize += n; this->MinTupleSize = n < this->MinTupleSize ? n : this->MinTupleSize; this->MaxTupleSize = n > this->MaxTupleSize ? n : this->MaxTupleSize; } void SortInfo::RunBuildPhase() { this->StopWatchTotal.start(); } void SortInfo::MergePhase() { this->AvgTupleSize += (float)this->TotalTupleSize / (float)this->TotalTupleCount; this->TimeRunPhase = StopWatchTotal.diffTimes(); this->StopWatchMerge.start(); } void SortInfo::Finished() { this->TimeMergePhase = StopWatchMerge.diffTimes(); this->TimeTotal = StopWatchTotal.diffTimes(); } ostream& operator<<(ostream& stream, SortInfo& info) { float totalTupleSizeMB = (float)info.TotalTupleSize / (float)(1024.0 * 1024.0); stream << endl << HLINE << endl << "Sort-Operation Statistics" << endl << HLINE << endl << "BufferSize: \t\t" << info.BufferSize << " ( " << info.BufferSize/1024 << " KByte)" << endl << "I/O-BufferSize: \t" << info.IOBufferSize << " ( " << info.IOBufferSize/1024 << " KByte)" << endl << "MaxMergeFanIn: \t\t" << info.MaxMergeFanIn << endl << "FirstMergeFanIn: \t" << info.F0 << endl << "Intermediate Merges: \t" << info.W+1 << endl << "Intermediate Tuples: \t" << info.IntermediateTuples << endl << "InitialRunsCount: \t" << info.InitialRunsCount << endl << "TotalTupleCount: \t" << info.TotalTupleCount << endl << "TotalSize: \t\t" << info.TotalTupleSize << " Byte ( " << totalTupleSizeMB << " MByte)" << endl << "MinTupleSize: \t\t" << info.MinTupleSize << " Byte" << endl << "MaxTupleSize: \t\t" << info.MaxTupleSize << " Byte" << endl << "AvgTupleSize: \t\t" << info.AvgTupleSize << " Byte" << endl << "TotalComparisons: \t" << info.TotalComparisons << endl << "TimeRunPhase: \t\t" << info.TimeRunPhase << endl << "TimeMergePhase: \t" << info.TimeMergePhase << endl << "TimeTotal: \t\t" << info.TimeTotal << endl; if ( info.RunStatistics == true ) { stream << HLINE << endl << "Initial run statistics (1-" << info.InitialRunsCount << ")" << endl << HLINE << endl; for (size_t i = 0; i < info.InitialRunInfo.size(); i++) { stream << *(info.InitialRunInfo[i]); } } if ( info.RunStatistics == true && !info.FinalRunInfo.empty() ) { stream << HLINE << endl << "Final run statistics" << endl << HLINE << endl; for (size_t i = 0; i < info.FinalRunInfo.size(); i++) { stream << *(info.FinalRunInfo[i]); } } stream << endl; return stream; } ostream& operator<<(ostream& os, SortedRun& run) { Tuple* t; os << "-------------------- Run " << run.GetRunNumber() << " -----------------" << endl; while ( ( t = run.GetNextTuple() ) != 0 ) { os << *t << endl; } return os; } /* 5 Implementation of class ~SortedRun~ */ SortedRun::SortedRun( int runNumber, int attributes, const SortOrderSpecification& spec, size_t ioBufferSize ) : runNumber(runNumber) , runLength(0) , tupleCount(0) , minTupleSize(UINT_MAX) , maxTupleSize(0) , buffer(0, ioBufferSize) , iter(0) , traceMode(RTFlag::isActive("ERA:TraceSort")) { heap = new TupleQueue(spec, attributes); if( traceMode ) { info = new SortedRunInfo(runNumber); } } SortedRun::~SortedRun() { if ( iter != 0 ) { delete iter; iter = 0; } if ( traceMode ) { // delete info; // Delete is handled in SortInfo Destructor info = 0; } if ( !heap->Empty() ) { std::vector& container = heap->GetContainer(); for(size_t i = 0; i < container.size(); i++) { container[i]->GetTuple()->DeleteIfAllowed(); } } delete heap; heap = 0; lastTuple.setTuple(0); } /* 6 Implementation of class ~SortProgressLocalInfo~ */ SortProgressLocalInfo::SortProgressLocalInfo() : ProgressLocalInfo() , intTuplesTotal(0) , intTuplesProc(0) { } /* 7 Implementation of class ~SortAlgorithm~ */ SortAlgorithm::SortAlgorithm( Word stream, const SortOrderSpecification& spec, SortProgressLocalInfo* p, Supplier s, size_t maxFanIn, size_t maxMemSize, size_t ioBufferSize) : F0(0) , W(0) , nextRunNumber(1) , checkProgressAfter(10) , stream(stream) , attributes(-1) , spec(spec) //, cmpObj(cmpObj) , usedMemory(0) , traceMode(RTFlag::isActive("ERA:TraceSort")) , traceModeExtended(RTFlag::isActive("ERA:TraceSortExtended")) , progress(p) { SortedRun *curRun = 0, *nextRun = 0; Word wTuple(Address(0)); // Check specified fan-in for a merge phase setMaxFanIn(maxFanIn); // Check specified main memory for this operation setMemory(maxMemSize, s); // Check I/O buffer size for this operation setIoBuffer(ioBufferSize); if ( traceMode ) { info = new SortInfo(MAX_MEMORY, IO_BUFFER_SIZE); info->RunBuildPhase(); info->MaxMergeFanIn = FMAX; TupleQueueCompare::ResetComparisonCounter(); } // Request first tuple and consume the stream completely qp->Request(stream.addr, wTuple); while( qp->Received(stream.addr) ) { Tuple *t = static_cast( wTuple.addr ); progress->read++; size_t memSize = t->GetMemSize(); // Save number of attributes if ( attributes == -1 ) { attributes = t->GetNoAttributes(); curRun = new SortedRun(nextRunNumber++, attributes, spec, IO_BUFFER_SIZE); runs.push_back(curRun); } if ( traceMode ) { info->UpdateStatistics(t->GetSize()); } if( usedMemory <= MAX_MEMORY ) { curRun->AppendToMemory(t); usedMemory += memSize; } else { if ( curRun->IsFirstTupleOnDisk() ) { // memory is completely used, append to disk progress->state = 1; curRun->AppendToDisk(t); } else { if ( curRun->IsInSortOrder(t) ) { // tuple is on sort order, append to disk curRun->AppendToDisk(t); } else { if ( traceMode ) { curRun->Info()->TuplesPassedToNextRun++; } // create next run if necessary if ( nextRun == 0 ) { nextRun = new SortedRun( nextRunNumber++, attributes, spec, IO_BUFFER_SIZE ); runs.push_back(nextRun); updateIntermediateMergeCost(); } // next tuple is smaller, save it for the next relation if ( !curRun->IsAtDisk() ) { // push minimum tuple of current run to disk curRun->AppendToDisk(); // append tuple to memory nextRun->AppendToMemory(t); } else { // finish current run curRun->RunFinished(); // switch runs curRun = nextRun; nextRun = 0; curRun->AppendToDisk(t); } } } } qp->Request(stream.addr, wTuple); } if ( traceMode ) { for (size_t i = 0; i < runs.size(); i++) { info->InitialRunInfo.push_back(runs[i]->InfoCopy()); info->InitialRunsCount = runs.size(); } } mergeInit(); } /* It may happen, that the localinfo object will be destroyed before all internal buffered tuples are delivered stream upwards, e.g. queries which use a ~head~ operator. In this case we need to delete also all tuples stored in memory. */ SortAlgorithm::~SortAlgorithm() { if ( traceMode ) { info->Finished(); info->TotalComparisons = TupleQueueCompare::GetComparisonCounter(); if ( traceModeExtended ) { for (size_t i = 0; i < runs.size(); i++) { info->FinalRunInfo.push_back(runs[i]->InfoCopy()); } } cmsg.info() << *info; cmsg.send(); delete info; info = 0; } while( !mergeQueue->Empty() ) { mergeQueue->Top()->GetTuple()->DeleteIfAllowed(); mergeQueue->Pop(); } delete mergeQueue; mergeQueue = 0; // delete information about sorted runs while ( !runs.empty() ) { delete (runs.back()); runs.pop_back(); } } void SortAlgorithm::setMemory(size_t maxMemory, Supplier s) { if ( maxMemory == UINT_MAX ) { MAX_MEMORY = qp->GetMemorySize(s) * 1024 * 1024; // in bytes } else { MAX_MEMORY = maxMemory; } // sort underestimates memory usage, add some security MAX_MEMORY = (size_t)(MAX_MEMORY*0.7); if(MAX_MEMORY < SORT_MINIMUM_MEMORY){ MAX_MEMORY = SORT_MINIMUM_MEMORY; } } void SortAlgorithm::setIoBuffer(size_t size) { if ( size == UINT_MAX && size > 16384 ) { IO_BUFFER_SIZE = WinUnix::getPageSize(); } else { IO_BUFFER_SIZE = size; } } void SortAlgorithm::setMaxFanIn(size_t f) { if ( f == UINT_MAX ) { FMAX = SORT_DEFAULT_MAX_FAN_IN; } else if ( f < SORT_MINIMUM_FAN_IN ) { FMAX = SORT_MINIMUM_FAN_IN; } else if ( f > SORT_MAXIMUM_FAN_IN ) { FMAX = SORT_MAXIMUM_FAN_IN; } else { FMAX = f; } } bool SortAlgorithm::updateIntermediateMergeCost() { int N = (int)runs.size(); if ( N > FMAX ) { // calculate merge fan-in for first merge phase so that the // following merge phases use the maximum merge fan-in this->F0 = ((N-FMAX-1) % (FMAX-1)) + 2; // calculate the number of merge phases using the maximum // merge fan-in this->W = (N-(F0-1)-FMAX) / (FMAX-1); if ( traceMode ) { info->F0 = F0; info->W = W; } progress->intTuplesTotal = calcIntermediateMergeCost(); return true; } return false; } int SortAlgorithm::calcIntermediateMergeCost() { if ( this->F0 > 0 ) { vector runSizes; // copy tuple counts of runs to array for(size_t i = 0; i < runs.size(); i++) { runSizes.push_back(runs[i]->GetTupleCount()); } int tuples = 0; // Simulate intermediate merge with fan-in F0 tuples += simulateIntermediateMerge(runSizes, this->F0); // Simulate intermediate merge with fan-in FMAX for(int i = 0; i < this->W; i++) { tuples += simulateIntermediateMerge(runSizes, this->FMAX); } return tuples; } return 0; } int SortAlgorithm::simulateIntermediateMerge(vector& arr, int f) { int cost = 0; assert(f <= (int)arr.size()); // sort run lengths in descending order std::sort(arr.begin(), arr.end(), greater()); // sum up tuples for this phase cost = sumLastN(arr, f); // cut off f runs arr.resize(arr.size() - f); // append merged run arr.push_back(cost); return cost; } // sum the last n elements of an integer array up int SortAlgorithm::sumLastN(vector& arr, int n) { int result = 0; assert(n <= (int)arr.size()); for(size_t i = arr.size() - n; i < arr.size(); i++) { result += arr[i]; } return result; } void SortAlgorithm::mergeInit() { if ( F0 > 0 ) { // final update of intermediate merge costs progress->intTuplesTotal = calcIntermediateMergeCost(); } if ( traceMode ) { info->MergePhase(); info->IntermediateTuples = progress->intTuplesTotal; } // create heap for merging runs mergeQueue = new TupleQueue(spec, attributes); // Check if we need intermediate merge phases if ( F0 > 0 ) { // Perform first intermediate merge phase with fan-in F0 mergeNShortest(F0); // Perform follow-up intermediate merge phases with fan-in FMAX for (int i = 0; i < W; i++) { mergeNShortest(FMAX); } } // Get first tuple from each run and push it into the merge heap for( size_t i = 0; i < runs.size(); i++ ) { Tuple *t = runs[i]->GetNextTuple(); if( t != 0 ) { mergeQueue->Push(t,i); } } } void SortAlgorithm::mergeNShortest(int n) { assert(n>1); assert((int)runs.size() > 1); assert((int)runs.size() >= n); Tuple* t; int counter = 0; SortedRunCompareLengthGreater comp; // sort the runs according to their length in bytes // a minimum heap is created make_heap(runs.begin(), runs.end(), comp); // get the n shortest runs vector merge; for (int i = 0; i < n; i++) { // pops first array element and moves it to the end of the array // the heap condition is fullfilled afterwards pop_heap(runs.begin(), runs.end(), comp); // add the popped run to the merge array merge.push_back(runs.back()); // delete run from runs array runs.pop_back(); } // create the sorted run for merging SortedRun* result = new SortedRun( nextRunNumber++, attributes, spec, IO_BUFFER_SIZE ); // append the sorted run to the end of the runs array runs.push_back(result); if ( traceModeExtended ) { cmsg.info() << HLINE << endl << "Intermediate merge phase into Run " << result->GetRunNumber() << " with " << n << " runs." << endl; } // Get first tuple from each run and push it into the merge heap. for( size_t i = 0; i < merge.size(); i++ ) { if( ( t = merge[i]->GetNextTuple() ) != 0 ) { mergeQueue->Push(t,i); } if ( traceModeExtended ) { cmsg.info() << merge[i]->GetRunNumber() << ": " << "RunLength: " << merge[i]->GetRunLength() / 1024 << " (KBytes), Tuples: " << merge[i]->GetTupleCount() << endl; } } // merge runs while ( ( t = this->nextResultTuple(merge) ) != 0 ) { result->AppendToDisk(t); progress->intTuplesProc++; // Check after a certain amount of tuples if a progress message is necessary // If we don't do this progress will freeze, because the query processors // eval method isn't called if ( ( counter++ % checkProgressAfter ) == 0) { qp->CheckProgress(); } } // delete merged runs for ( size_t i = 0; i < merge.size(); i++) { if ( traceModeExtended ) { cmsg.info() << "Deleting Run " << merge[i]->GetRunNumber() << endl; cmsg.send(); } delete merge[i]; } merge.clear(); return; } Tuple* SortAlgorithm::NextResultTuple() { return nextResultTuple(runs); } Tuple* SortAlgorithm::nextResultTuple(vector& arr) { if( mergeQueue->Empty() ) // stream finished { return 0; } else { // Take the next tuple from the merge queue and set // it as the result tuple Tuple* result = mergeQueue->Top()->GetTuple(); int pos = mergeQueue->Top()->GetPosition(); mergeQueue->Pop(); // Refill the merge queue with a tuple from result // tuple's queue if there are more Tuple* t = arr[pos]->GetNextTuple(); // Push the found tuple into the merge queue if( t != 0 ) { // run not finished mergeQueue->Push(t, pos); } return result; } } /* 8 Implementation of value mapping function of operator ~sort2~ and ~sortby2~ */ template int SortValueMap(Word* args, Word& result, int message, Word& local, Supplier s) { bool traceProgress = false; // Operator sort2 (firstArg = 1, param = false) // args[0] : stream // args[1] : the number of sort attributes // args[3] : the index of the first sort attribute // args[4] : a boolean which indicates if sortorder should // be asc (true) or desc (false) // args[5] : Same as 3 but for the second sort attribute // args[6] : Same as 4 but for the second sort attribute // .... // Operator sort2Param (firstArg = 4, param = true) // args[0] : stream // args[1] : operator's main memory in bytes // args[2] : maximum fan-in of merge phase // args[3] : I/O buffer size in bytes // args[4] : the number of sort attributes // args[5] : the index of the first sort attribute // args[6] : a boolean which indicates if sortorder should // be asc (true) or desc (false) // args[7] : Same as 3 but for the second sort attribute // args[8] : Same as 4 but for the second sort attribute // .... // Operator sortby2 (firstArg = 2, param = false) // args[0] : stream // args[1] : sort attributes specification as list (ignored) // args[2] : the number of sort attributes // args[3] : the index of the first sort attribute // args[4] : a boolean which indicates if sortorder should // be asc (true) or desc (false) // args[5] : Same as 3 but for the second sort attribute // args[6] : Same as 4 but for the second sort attribute // .... // Operator sortby2Param (firstArg = 5, param = true) // args[0] : stream // args[1] : sort attributes specification as list (ignored) // args[2] : operator's main memory in bytes // args[3] : maximum fan-in of merge phase // args[4] : I/O buffer size in bytes // args[5] : the number of sort attributes // args[6] : the index of the first sort attribute // args[7] : a boolean which indicates if sortorder should // be asc (true) or desc (false) // args[8] : Same as 3 but for the second sort attribute // args[9] : Same as 4 but for the second sort attribute // .... SortLocalInfo* li; li = static_cast( local.addr ); switch(message) { case OPEN: { if (li) { delete li; } li = new SortLocalInfo(); local.addr = li; // at this point the local value is well defined // afterwards progress request calls are // allowed. li->ptr = 0; qp->Open(args[0].addr); return 0; } case REQUEST: { if ( li->ptr == 0 ) { SortOrderSpecification spec; int nAttrCount = StdTypes::GetInt( args[firstArg] ); for(int i = 1; i <= nAttrCount; i++) { int j = firstArg + (2*i - 1); int attrIndex = StdTypes::GetInt( args[j] ); bool isAsc = StdTypes::GetBool( args[j+1] ); spec.push_back(pair(attrIndex, isAsc)); }; //Sorting is done in the following constructor. It was moved from //OPEN to REQUEST to avoid long delays in the OPEN method, which are //a problem for progress estimation if ( param ) { int idx = firstArg - 3; int maxMemSize = StdTypes::GetInt( args[idx] ); int maxFanIn = StdTypes::GetInt( args[idx+1] ); int ioBufferSize = StdTypes::GetInt( args[idx+2] ); size_t fan = maxFanIn < 0 ? UINT_MAX : (size_t)maxFanIn; size_t mem = maxMemSize < 0 ? UINT_MAX : (size_t)maxMemSize; size_t buf = ioBufferSize < 0 ? UINT_MAX : (size_t)ioBufferSize; li->ptr = new SortAlgorithm(args[0], spec, li, s, fan, mem, buf); } else { li->ptr = new SortAlgorithm(args[0], spec, li, s); } } SortAlgorithm* sa = li->ptr; result.setAddr( sa->NextResultTuple() ); li->returned++; return result.addr != 0 ? YIELD : CANCEL; } case CLOSE: { // Note: object deletion is handled in OPEN and CLOSEPROGRESS qp->Close(args[0].addr); return 0; } case CLOSEPROGRESS: { if (li) { delete li; local.addr = 0; } return 0; } case REQUESTPROGRESS: { ProgressInfo p1; ProgressInfo *pRes; const double uSortBy = 0.0000873; //millisecs per byte input and sort // old constant 0.000396; const double vSortBy = 0.0000243; //millisecs per byte output // old constant 0.000194; const double oSortBy = 0.00004; //offset due to writing to disk //not yet measurable // constants determined in file ExtRelation-C++/ConstantsExtendStream pRes = (ProgressInfo*) result.addr; if( !li ) { return CANCEL; } else { if (qp->RequestProgress(args[0].addr, &p1)) { // ------------------------------------------- // Result cardinality // ------------------------------------------- pRes->Card = li->returned == 0 ? p1.Card : li->read; pRes->CopySizes(p1); // ------------------------------------------- // Total time // ------------------------------------------- pRes->Time = p1.Time + pRes->Card * p1.SizeExt * (uSortBy + oSortBy * li->state) + pRes->Card * p1.SizeExt * vSortBy + li->intTuplesTotal * p1.Size * (2 * vSortBy); // ------------------------------------------- // Total progress // ------------------------------------------- pRes->Progress = (p1.Progress * p1.Time + li->read * p1.SizeExt * (uSortBy + oSortBy * li->state) + li->returned * p1.SizeExt * vSortBy + li->intTuplesProc * p1.SizeExt * (2 * vSortBy) ) / pRes->Time; // ------------------------------------------- // Blocking time // ------------------------------------------- // Estimated time until first result tuple is ready pRes->BTime = p1.Time + pRes->Card * p1.SizeExt * (uSortBy + oSortBy * li->state) + li->intTuplesTotal * p1.SizeExt * (2 * vSortBy); // ------------------------------------------- // Blocking progress // ------------------------------------------- pRes->BProgress = (p1.Progress * p1.Time + li->read * p1.SizeExt * (uSortBy + oSortBy * li->state) + li->intTuplesProc * p1.SizeExt * (2 * vSortBy)) / pRes->BTime; if ( traceProgress ) { cmsg.info() << "p1->Card: " << p1.Card << ", p1.Time: " << p1.Time << ", p1.Progress: " << p1.Progress << endl << "pRes->Time: " << pRes->Time << ", pRes->Progress: " << pRes->Progress << endl << ", intTuples: " << li->intTuplesProc << "/" << li->intTuplesTotal << endl; cmsg.send(); } return YIELD; } else { return CANCEL; } } } } return 0; } /* 9 Instantiation of Template Functions For some reasons the compiler cannot expand these template functions in the file ~ExtRelation2Algebra.cpp~, thus the value mapping functions are instantiated here. */ template int SortValueMap<1, false>( Word* args, Word& result, int message, Word& local, Supplier s ); template int SortValueMap<2, false >( Word* args, Word& result, int message, Word& local, Supplier s ); template int SortValueMap<4, true>( Word* args, Word& result, int message, Word& local, Supplier s ); template int SortValueMap<5, true>( Word* args, Word& result, int message, Word& local, Supplier s ); } // end of namespace extrel2