Skip to content

Commit f9495f2

Browse files
BP5Writer: Improvements to rerouting aggregation scheme
Previously the buffer pool for non-blocking sends was a fixed size (set at 100), and had it no guards against re-using (and thus, obliterating) a buffer while a previous ISend using the buffer may still have been incomplete. This commit introduces a role-appropriate pool size, and also adds logic that forces the next ISend to wait if all buffers in the pool are actively being used by a prior ISend. To reduce the likelihood of the background messaging thread spinning and consuming CPU while nothing is happening, this commit introduces a conditional yield within the background thread loop. If no work is done during an iteration of the loop (message receipt and response, rerouting pair identified and messaged, etc...) then yield() is called at the end of the loop. MPI send-to-self can sometimes be slow, and it was used by each subcoordinator to signal itself to write at the beginning of the workflow. This commit replaces the self-send with direct signalling of the main thread to begin writing. Previously, once the loop finished, the thread exited immediately, possibly causing buffers associated with incomplete ISends to be destroyed. Now that we're collecting the request object from each ISend (stored in the buffer pool), this commit iterates the pool one final time to wait for any incomplete ISend to complete.
1 parent 36f0b6e commit f9495f2

1 file changed

Lines changed: 148 additions & 68 deletions

File tree

source/adios2/engine/bp5/BP5Writer_WithRerouting.cpp

Lines changed: 148 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222

2323
namespace
2424
{
25+
struct ManagedBuffer
26+
{
27+
std::vector<char> m_Data;
28+
adios2::helper::Comm::Req m_Request;
29+
bool m_Active = false;
30+
};
2531

2632
class BufferPool
2733
{
@@ -30,24 +36,54 @@ class BufferPool
3036

3137
~BufferPool() = default;
3238

33-
std::vector<char> &GetNextBuffer()
39+
ManagedBuffer &GetNextBuffer()
3440
{
35-
size_t bufferIdx = m_CurrentBufferIdx;
41+
size_t startIdx = m_CurrentBufferIdx;
3642

37-
if (m_CurrentBufferIdx < m_Pool.size() - 1)
43+
while (true)
3844
{
39-
m_CurrentBufferIdx += 1;
45+
ManagedBuffer &mb = m_Pool[m_CurrentBufferIdx];
46+
47+
// Advance index for next call
48+
m_CurrentBufferIdx = (m_CurrentBufferIdx + 1) % m_Pool.size();
49+
50+
// If buffer was never used, it's safe
51+
if (!mb.m_Active)
52+
{
53+
return mb;
54+
}
55+
56+
// Check if the previous MPI_Isend using this buffer is complete
57+
if (mb.m_Request.Test())
58+
{
59+
mb.m_Active = false;
60+
return mb;
61+
}
62+
63+
// If we've circled the whole pool and none have completed, we MUST wait
64+
if (m_CurrentBufferIdx == startIdx)
65+
{
66+
mb.m_Request.Wait();
67+
mb.m_Active = false;
68+
return mb;
69+
}
4070
}
41-
else
71+
}
72+
73+
void Flush()
74+
{
75+
for (auto &mb : m_Pool)
4276
{
43-
m_CurrentBufferIdx = 0;
77+
if (mb.m_Active)
78+
{
79+
mb.m_Request.Wait();
80+
mb.m_Active = false;
81+
}
4482
}
45-
46-
return m_Pool[bufferIdx];
4783
}
4884

4985
size_t m_CurrentBufferIdx = 0;
50-
std::vector<std::vector<char>> m_Pool;
86+
std::vector<ManagedBuffer> m_Pool;
5187
};
5288

5389
struct WriterGroupState
@@ -186,30 +222,7 @@ void BP5Writer::ReroutingCommunicationLoop()
186222
m_Profiler.AddTimerWatch("Rerouting_Send_NB");
187223
m_Profiler.AddTimerWatch("Rerouting_Send_B");
188224
m_Profiler.AddTimerWatch("Rerouting_Recv_NB");
189-
190-
auto lf_SendNonBlocking = [](RerouteMessage &msg, adios2::helper::Comm &comm, int toRank,
191-
std::vector<char> &buffer,
192-
adios2::profiling::JSONProfiler &profiler) {
193-
profiler.Start("Rerouting_Send_NB");
194-
msg.NonBlockingSendTo(comm, toRank, buffer);
195-
profiler.Stop("Rerouting_Send_NB");
196-
};
197-
198-
auto lf_SendBlocking = [](RerouteMessage &msg, adios2::helper::Comm &comm, int toRank,
199-
std::vector<char> &buffer,
200-
adios2::profiling::JSONProfiler &profiler) {
201-
profiler.Start("Rerouting_Send_B");
202-
msg.BlockingSendTo(comm, toRank, buffer);
203-
profiler.Stop("Rerouting_Send_B");
204-
};
205-
206-
auto lf_RecvBlocking = [](RerouteMessage &msg, adios2::helper::Comm &comm, int fromRank,
207-
std::vector<char> &buffer,
208-
adios2::profiling::JSONProfiler &profiler) {
209-
profiler.Start("Rerouting_Recv_NB");
210-
msg.BlockingRecvFrom(comm, fromRank, buffer);
211-
profiler.Stop("Rerouting_Recv_NB");
212-
};
225+
m_Profiler.AddTimerWatch("Rerouting_GetNextBuffer");
213226

214227
int subCoord = m_Aggregator->m_AggregatorRank;
215228
bool iAmSubCoord = m_RankMPI == subCoord;
@@ -234,8 +247,21 @@ void BP5Writer::ReroutingCommunicationLoop()
234247

235248
// Most sends are currently non-blocking. We use the pool to avoid a
236249
// situation where the buffer is destructed before the send is complete.
237-
BufferPool sendBuffers(100);
238-
std::vector<char> recvBuffer;
250+
size_t poolSize = 1;
251+
if (iAmGlobalCoord)
252+
{
253+
// twice the number of subcoordinators
254+
poolSize = 2 * m_Partitioning.m_Partitions.size();
255+
}
256+
else if (iAmSubCoord)
257+
{
258+
// twice the number of ranks in my writer chain/group
259+
poolSize = 2 * m_Partitioning.m_Partitions[m_Aggregator->m_SubStreamIndex].size();
260+
}
261+
BufferPool sendBuffers(poolSize);
262+
263+
std::vector<char> recvBuffer; // For blocking recvs
264+
std::vector<char> sendBuffer; // For blocking sends
239265
int writingRank = -1;
240266
uint64_t currentFilePos = 0;
241267
uint64_t writeMoreCount = 0;
@@ -247,6 +273,32 @@ void BP5Writer::ReroutingCommunicationLoop()
247273
bool expectingWriteCompletion = false;
248274
bool sentIdle = false;
249275

276+
auto lf_SendNonBlocking = [&sendBuffers](RerouteMessage &msg, adios2::helper::Comm &comm,
277+
int toRank,
278+
adios2::profiling::JSONProfiler &profiler) {
279+
profiler.Start("Rerouting_Send_NB");
280+
profiler.Start("Rerouting_GetNextBuffer");
281+
ManagedBuffer &buffer = sendBuffers.GetNextBuffer();
282+
profiler.Stop("Rerouting_GetNextBuffer");
283+
buffer.m_Request = msg.NonBlockingSendTo(comm, toRank, buffer.m_Data);
284+
buffer.m_Active = true;
285+
profiler.Stop("Rerouting_Send_NB");
286+
};
287+
288+
auto lf_SendBlocking = [&sendBuffer](RerouteMessage &msg, adios2::helper::Comm &comm,
289+
int toRank, adios2::profiling::JSONProfiler &profiler) {
290+
profiler.Start("Rerouting_Send_B");
291+
msg.BlockingSendTo(comm, toRank, sendBuffer);
292+
profiler.Stop("Rerouting_Send_B");
293+
};
294+
295+
auto lf_RecvBlocking = [&recvBuffer](RerouteMessage &msg, adios2::helper::Comm &comm,
296+
int fromRank, adios2::profiling::JSONProfiler &profiler) {
297+
profiler.Start("Rerouting_Recv_NB");
298+
msg.BlockingRecvFrom(comm, fromRank, recvBuffer);
299+
profiler.Stop("Rerouting_Recv_NB");
300+
};
301+
250302
if (iAmGlobalCoord)
251303
{
252304
// Global coordinator initializes the state it tracks for each subcoord
@@ -299,6 +351,7 @@ void BP5Writer::ReroutingCommunicationLoop()
299351

300352
while (lf_keepGoing())
301353
{
354+
bool workDone = false; // Flag to decide whether to yield on this iteration
302355
int msgReady = 0;
303356
helper::Comm::Status status =
304357
m_Comm.Iprobe(static_cast<int>(helper::Comm::Constants::CommRecvAny), 0, &msgReady);
@@ -307,7 +360,8 @@ void BP5Writer::ReroutingCommunicationLoop()
307360
if (msgReady)
308361
{
309362
RerouteMessage message;
310-
lf_RecvBlocking(message, m_Comm, status.Source, recvBuffer, m_Profiler);
363+
lf_RecvBlocking(message, m_Comm, status.Source, m_Profiler);
364+
workDone = true;
311365

312366
switch ((RerouteMessage::MessageType)message.m_MsgType)
313367
{
@@ -383,8 +437,7 @@ void BP5Writer::ReroutingCommunicationLoop()
383437
closeAckMsg.m_SrcRank = m_RankMPI;
384438
closeAckMsg.m_DestRank = globalCoord;
385439
closeAckMsg.m_WildCard = static_cast<int>(m_Aggregator->m_SubStreamIndex);
386-
lf_SendBlocking(closeAckMsg, m_Comm, globalCoord, sendBuffers.GetNextBuffer(),
387-
m_Profiler);
440+
lf_SendBlocking(closeAckMsg, m_Comm, globalCoord, m_Profiler);
388441
break;
389442
case RerouteMessage::MessageType::GROUP_CLOSE_ACK:
390443
// msg for global coordinator
@@ -433,8 +486,7 @@ void BP5Writer::ReroutingCommunicationLoop()
433486
inquiryMsg.m_MsgType = RerouteMessage::MessageType::STATUS_INQUIRY;
434487
inquiryMsg.m_SrcRank = m_RankMPI;
435488
inquiryMsg.m_DestRank = scRank;
436-
lf_SendNonBlocking(inquiryMsg, m_Comm, scRank,
437-
sendBuffers.GetNextBuffer(), m_Profiler);
489+
lf_SendNonBlocking(inquiryMsg, m_Comm, scRank, m_Profiler);
438490
}
439491
}
440492

@@ -471,8 +523,7 @@ void BP5Writer::ReroutingCommunicationLoop()
471523
// The response to the status query is my subfile and queue size
472524
replyMsg.m_WildCard = static_cast<int>(m_Aggregator->m_SubStreamIndex);
473525
replyMsg.m_Size = static_cast<uint64_t>(writerQueue.size());
474-
lf_SendNonBlocking(replyMsg, m_Comm, globalCoord, sendBuffers.GetNextBuffer(),
475-
m_Profiler);
526+
lf_SendNonBlocking(replyMsg, m_Comm, globalCoord, m_Profiler);
476527
break;
477528
case RerouteMessage::MessageType::STATUS_REPLY:
478529
if (m_Parameters.verbose > 3)
@@ -507,8 +558,7 @@ void BP5Writer::ReroutingCommunicationLoop()
507558
rejectMsg.m_MsgType = RerouteMessage::MessageType::REROUTE_REJECT;
508559
rejectMsg.m_SrcRank = message.m_SrcRank;
509560
rejectMsg.m_DestRank = message.m_DestRank;
510-
lf_SendNonBlocking(rejectMsg, m_Comm, globalCoord, sendBuffers.GetNextBuffer(),
511-
m_Profiler);
561+
lf_SendNonBlocking(rejectMsg, m_Comm, globalCoord, m_Profiler);
512562
}
513563
else
514564
{
@@ -527,8 +577,7 @@ void BP5Writer::ReroutingCommunicationLoop()
527577
ackMsg.m_SrcRank = message.m_SrcRank;
528578
ackMsg.m_DestRank = message.m_DestRank;
529579
ackMsg.m_WildCard = reroutedRank;
530-
lf_SendNonBlocking(ackMsg, m_Comm, globalCoord, sendBuffers.GetNextBuffer(),
531-
m_Profiler);
580+
lf_SendNonBlocking(ackMsg, m_Comm, globalCoord, m_Profiler);
532581
}
533582
break;
534583
case RerouteMessage::MessageType::REROUTE_REJECT:
@@ -579,8 +628,7 @@ void BP5Writer::ReroutingCommunicationLoop()
579628
adios2::helper::RerouteMessage writeMoreMsg;
580629
writeMoreMsg.m_MsgType = RerouteMessage::MessageType::WRITE_MORE;
581630
writeMoreMsg.m_WildCard = message.m_WildCard; // i.e. the rerouted writer rank
582-
lf_SendNonBlocking(writeMoreMsg, m_Comm, message.m_DestRank,
583-
sendBuffers.GetNextBuffer(), m_Profiler);
631+
lf_SendNonBlocking(writeMoreMsg, m_Comm, message.m_DestRank, m_Profiler);
584632

585633
groupIdlesNeeded.insert(message.m_DestRank);
586634

@@ -643,8 +691,7 @@ void BP5Writer::ReroutingCommunicationLoop()
643691
// done at this point. However, I need to do a blocking send because I
644692
// am about to return from this function, at which point my buffer pool
645693
// goes away.
646-
lf_SendBlocking(writeCompleteMsg, m_Comm, m_TargetCoordinator,
647-
sendBuffers.GetNextBuffer(), m_Profiler);
694+
lf_SendBlocking(writeCompleteMsg, m_Comm, m_TargetCoordinator, m_Profiler);
648695

649696
receivedGroupClose = true;
650697
continue;
@@ -657,10 +704,10 @@ void BP5Writer::ReroutingCommunicationLoop()
657704
<< m_TargetCoordinator << ") of write completion -- NONBLOCKING"
658705
<< std::endl;
659706
}
660-
lf_SendNonBlocking(writeCompleteMsg, m_Comm, m_TargetCoordinator,
661-
sendBuffers.GetNextBuffer(), m_Profiler);
707+
lf_SendNonBlocking(writeCompleteMsg, m_Comm, m_TargetCoordinator, m_Profiler);
662708
}
663709

710+
workDone = true;
664711
sentFinished = true;
665712
}
666713
}
@@ -679,16 +726,34 @@ void BP5Writer::ReroutingCommunicationLoop()
679726
<< std::endl;
680727
}
681728
writerQueue.pop();
682-
adios2::helper::RerouteMessage writeMsg;
683-
writeMsg.m_MsgType = RerouteMessage::MessageType::DO_WRITE;
684-
writeMsg.m_SrcRank = m_RankMPI;
685-
writeMsg.m_DestRank = nextWriter;
686-
writeMsg.m_WildCard = static_cast<int>(m_Aggregator->m_SubStreamIndex);
687-
writeMsg.m_Offset = currentFilePos;
688-
writingRank = nextWriter;
689-
expectingWriteCompletion = true;
690-
lf_SendNonBlocking(writeMsg, m_Comm, nextWriter, sendBuffers.GetNextBuffer(),
691-
m_Profiler);
729+
730+
if (nextWriter == m_RankMPI)
731+
{
732+
// Don't use MPI to tell ourselves to write
733+
std::unique_lock<std::mutex> lck(m_WriteMutex);
734+
m_TargetIndex = static_cast<int>(m_Aggregator->m_SubStreamIndex);
735+
m_DataPos = currentFilePos;
736+
m_TargetCoordinator = m_RankMPI;
737+
m_ReadyToWrite = true;
738+
m_WriteCV.notify_one();
739+
740+
writingRank = m_RankMPI;
741+
expectingWriteCompletion = true;
742+
}
743+
else
744+
{
745+
adios2::helper::RerouteMessage writeMsg;
746+
writeMsg.m_MsgType = RerouteMessage::MessageType::DO_WRITE;
747+
writeMsg.m_SrcRank = m_RankMPI;
748+
writeMsg.m_DestRank = nextWriter;
749+
writeMsg.m_WildCard = static_cast<int>(m_Aggregator->m_SubStreamIndex);
750+
writeMsg.m_Offset = currentFilePos;
751+
writingRank = nextWriter;
752+
expectingWriteCompletion = true;
753+
lf_SendNonBlocking(writeMsg, m_Comm, nextWriter, m_Profiler);
754+
}
755+
756+
workDone = true;
692757
}
693758
else if (!sentIdle)
694759
{
@@ -713,9 +778,9 @@ void BP5Writer::ReroutingCommunicationLoop()
713778
idleMsg.m_SrcRank = m_RankMPI;
714779
idleMsg.m_DestRank = globalCoord;
715780
idleMsg.m_WildCard = static_cast<int>(m_Aggregator->m_SubStreamIndex);
716-
lf_SendNonBlocking(idleMsg, m_Comm, globalCoord, sendBuffers.GetNextBuffer(),
717-
m_Profiler);
781+
lf_SendNonBlocking(idleMsg, m_Comm, globalCoord, m_Profiler);
718782
sentIdle = true;
783+
workDone = true;
719784
}
720785
}
721786

@@ -751,11 +816,12 @@ void BP5Writer::ReroutingCommunicationLoop()
751816
rerouteReqMsg.m_MsgType = RerouteMessage::MessageType::REROUTE_REQUEST;
752817
rerouteReqMsg.m_SrcRank = writerSubcoordRank;
753818
rerouteReqMsg.m_DestRank = idleSubcoordRank;
754-
lf_SendNonBlocking(rerouteReqMsg, m_Comm, writerSubcoordRank,
755-
sendBuffers.GetNextBuffer(), m_Profiler);
819+
lf_SendNonBlocking(rerouteReqMsg, m_Comm, writerSubcoordRank, m_Profiler);
756820

757821
groupState[idleIdx].m_currentStatus = WriterGroupState::Status::PENDING;
758822
groupState[writerIdx].m_currentStatus = WriterGroupState::Status::PENDING;
823+
824+
workDone = true;
759825
}
760826
else if (result == StateTraversal::SearchResult::FINISHED && groupIdlesNeeded.empty())
761827
{
@@ -785,8 +851,7 @@ void BP5Writer::ReroutingCommunicationLoop()
785851
}
786852
adios2::helper::RerouteMessage closeMsg;
787853
closeMsg.m_MsgType = RerouteMessage::MessageType::GROUP_CLOSE;
788-
lf_SendNonBlocking(closeMsg, m_Comm, subCoordRanks[scIdx],
789-
sendBuffers.GetNextBuffer(), m_Profiler);
854+
lf_SendNonBlocking(closeMsg, m_Comm, subCoordRanks[scIdx], m_Profiler);
790855
}
791856
}
792857

@@ -858,6 +923,12 @@ void BP5Writer::ReroutingCommunicationLoop()
858923
}
859924
}
860925
}
926+
927+
if (!workDone)
928+
{
929+
// Yield to avoid busy-cycling the CPU when nothing productive was done
930+
std::this_thread::yield();
931+
}
861932
}
862933

863934
// Before leaving this method, subcoordinators need to update the variable tracking
@@ -867,6 +938,15 @@ void BP5Writer::ReroutingCommunicationLoop()
867938
m_DataPos = currentFilePos;
868939
}
869940

941+
if (m_Parameters.verbose > 3)
942+
{
943+
std::cout << "Rank " << m_RankMPI << " flushing pending sends..." << std::endl;
944+
}
945+
946+
// Ensure all ISends associated with buffers in the pool are complete
947+
// before we let the pool go out of scope
948+
sendBuffers.Flush();
949+
870950
if (m_Parameters.verbose > 3)
871951
{
872952
std::cout << "Rank " << m_RankMPI << " Exit ReroutingCommunicationLoop" << std::endl;

0 commit comments

Comments
 (0)