Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion thorlcr/activities/indexread/thindexread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ class CIndexCountActivityMaster : public CIndexReadBase
typedef CIndexReadBase PARENT;

IHThorIndexCountArg *helper;
mptag_t stopTag = TAG_NULL;

void processKeyedLimit()
{
Expand All @@ -385,10 +386,55 @@ class CIndexCountActivityMaster : public CIndexReadBase
}
}
}
rowcount_t aggregateToLimitIndexExists()
{
// Special version for IndexExists (choosenLimit == 1)
// When any slave returns count > 0, signal all slaves to stop
rowcount_t total = 0;
unsigned slaves = container.queryJob().querySlaves();
bool sentStop = false;
ICommunicator &comm = queryJobChannel().queryJobComm();

for (unsigned s=0; s<slaves; s++)
{
CMessageBuffer msg;
rank_t sender;
if (!receiveMsg(msg, RANK_ALL, mpTag, &sender))
return 0;
if (abortSoon)
return 0;
rowcount_t count;
msg.read(count);
total += count;

// If we found a match and haven't sent stop signal yet
if (!sentStop && total > 0)
{
sentStop = true;
// Broadcast stop signal to all slaves
CMessageBuffer stopMsg;
stopMsg.append(true); // stop flag
for (unsigned i=0; i<slaves; i++)
{
comm.send(stopMsg, i+1, stopTag);
}
}
}
return total;
}
public:
CIndexCountActivityMaster(CMasterGraphElement *info) : CIndexReadBase(info)
{
helper = (IHThorIndexCountArg *)queryHelper();
// Allocate stop tag for IndexExists early termination
if (!container.queryLocalOrGrouped())
stopTag = container.queryJob().allocateMPTag();
}
virtual void serializeSlaveData(MemoryBuffer &dst, unsigned slave) override
{
CIndexReadBase::serializeSlaveData(dst, slave);
// Always send stopTag (will be TAG_NULL for non-IndexExists or local/grouped cases)
dst.append(stopTag);
}
virtual void process() override
{
Expand All @@ -399,12 +445,30 @@ class CIndexCountActivityMaster : public CIndexReadBase
keyedLimit = (rowcount_t)helper->getKeyedLimit();
if (keyedLimit != RCMAX)
processKeyedLimit();
rowcount_t total = aggregateToLimit();

rowcount_t total;
rowcount_t choosenLimit = helper->getChooseNLimit();
if (choosenLimit == 1)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot - In fact, this should not be the basis of stopping early.
In all cases, if the limit is exceeded, the master should signal to the workers that they can stop to save them wasting their time.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified to send stop signals whenever ANY choosenLimit is exceeded (not just choosenLimit==1). The master now checks total > choosenLimit in the overridden aggregateToLimit() method, making this optimization broadly applicable to CHOOSEN, IndexExists, and any other operation with a limit. See commit for details.

{
// IndexExists case - use optimized aggregation
total = aggregateToLimitIndexExists();
}
else
{
total = aggregateToLimit();
}

CMessageBuffer msg;
msg.append(total);
ICommunicator &comm = queryJobChannel().queryJobComm();
verifyex(comm.send(msg, 1, mpTag)); // send to 1st slave only
}
virtual void abort() override
{
CIndexReadBase::abort();
if (stopTag != TAG_NULL)
cancelReceiveMsg(RANK_ALL, stopTag);
}
};

CActivityBase *createIndexCountActivityMaster(CMasterGraphElement *info)
Expand Down
41 changes: 41 additions & 0 deletions thorlcr/activities/indexread/thindexreadslave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "jfile.hpp"
#include "jtime.hpp"
#include "jsort.hpp"
#include <atomic>

#include "rtlkey.hpp"
#include "jhtree.hpp"
Expand Down Expand Up @@ -1273,6 +1274,8 @@ class CIndexCountSlaveActivity : public CIndexReadSlaveBase
rowcount_t preknownTotalCount = 0;
bool totalCountKnown = false;
bool done = false;
mptag_t stopTag = TAG_NULL;
std::atomic<bool> stopped{false};

bool checkKeyedLimit()
{
Expand All @@ -1285,12 +1288,41 @@ class CIndexCountSlaveActivity : public CIndexReadSlaveBase
}
return true;
}
bool checkStopSignal()
{
// For IndexExists (choosenLimit == 1), check if master signaled us to stop
if (stopped.load(std::memory_order_relaxed))
return true;

if (choosenLimit == 1 && stopTag != TAG_NULL)
{
// Non-blocking check for stop signal
CMessageBuffer msg;
if (container.queryJobChannel().queryJobComm().recv(msg, 0, stopTag, nullptr, 0))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot - this approach won't work. Even if this was non-blocking (which it isn't), it is too inefficient for the caller of checkStopSignal() to do this each call.
There probably needs to be an asynchronous thread that is listening for stop signals, that flags 'stopped' so the count routines can bail out when it sees the state.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to use a dedicated async CStopHandler thread instead of polling. The thread blocks on recv() waiting for stop signals and sets an atomic flag that the counting loop checks. This eliminates the inefficient per-call recv() overhead. See commit for details.

{
bool stopFlag;
msg.read(stopFlag);
if (stopFlag)
{
stopped.store(true, std::memory_order_relaxed);
return true;
}
}
}
return false;
}
public:
CIndexCountSlaveActivity(CGraphElementBase *_container) : CIndexReadSlaveBase(_container)
{
helper = static_cast <IHThorIndexCountArg *> (container.queryHelper());
appendOutputLinked(this);
}
virtual void init(MemoryBuffer &data, MemoryBuffer &slaveData) override
{
PARENT::init(data, slaveData);
// Always read stopTag (will be TAG_NULL for non-IndexExists or local/grouped cases)
data.read(stopTag);
}
virtual void prepareManager(IKeyManager *manager) override
{
PARENT::prepareManager(manager);
Expand Down Expand Up @@ -1323,6 +1355,7 @@ class CIndexCountSlaveActivity : public CIndexReadSlaveBase
preknownTotalCount = 0;
}
done = false;
stopped.store(false, std::memory_order_relaxed);
}

// IRowStream
Expand Down Expand Up @@ -1371,11 +1404,17 @@ class CIndexCountSlaveActivity : public CIndexReadSlaveBase
callback.finishedRow();
if ((totalCount > choosenLimit))
break;
// For IndexExists, check if master signaled us to stop
if (checkStopSignal())
break;
}
if (keyManager)
resetManager(keyManager);
if ((totalCount > choosenLimit))
break;
// For IndexExists, check if master signaled us to stop
if (checkStopSignal())
break;
}
if (_currentManager)
prepareManager(_currentManager);
Expand Down Expand Up @@ -1435,6 +1474,8 @@ class CIndexCountSlaveActivity : public CIndexReadSlaveBase
{
CIndexReadSlaveBase::abort();
cancelReceiveMsg(0, mpTag);
if (stopTag != TAG_NULL)
cancelReceiveMsg(0, stopTag);
}
};

Expand Down