Skip to content

Commit 96e2f45

Browse files
committed
DPL: avoid MessageSet abstractions when forwarding
This is most likely faster, and it will allow us to move the early forwarding at an earlier stage where the data is not yet in a MessageSet.
1 parent 4c3ba3d commit 96e2f45

File tree

4 files changed

+127
-99
lines changed

4 files changed

+127
-99
lines changed

Framework/Core/include/Framework/DataProcessingHelpers.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "Framework/TimesliceIndex.h"
1717
#include <fairmq/FwdDecls.h>
1818
#include <vector>
19+
#include <span>
1920

2021
namespace o2::framework
2122
{
@@ -53,9 +54,11 @@ struct DataProcessingHelpers {
5354
/// starts the EoS timers and returns the new TransitionHandlingState in case as new state is requested
5455
static TransitionHandlingState updateStateTransition(ServiceRegistryRef const& ref, ProcessingPolicies const& policies);
5556
/// Helper to route messages for forwarding
56-
static std::vector<fair::mq::Parts> routeForwardedMessages(FairMQDeviceProxy& proxy,
57-
std::vector<MessageSet>& currentSetOfInputs,
58-
const bool copyByDefault, bool consume);
57+
static std::vector<fair::mq::Parts> routeForwardedMessageSet(FairMQDeviceProxy& proxy, std::vector<MessageSet>& currentSetOfInputs,
58+
bool copy, bool consume);
59+
/// Helper to route messages for forwarding
60+
static void routeForwardedMessages(FairMQDeviceProxy& proxy, std::span<fair::mq::MessagePtr>& currentSetOfInputs, std::vector<fair::mq::Parts>& forwardedParts,
61+
bool copy, bool consume);
5962
};
6063
} // namespace o2::framework
6164
#endif // O2_FRAMEWORK_DATAPROCESSINGHELPERS_H_

Framework/Core/src/DataProcessingDevice.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ static auto forwardInputs = [](ServiceRegistryRef registry, TimesliceSlot slot,
592592
O2_SIGNPOST_ID_GENERATE(sid, forwarding);
593593
O2_SIGNPOST_START(forwarding, sid, "forwardInputs", "Starting forwarding for slot %zu with oldestTimeslice %zu %{public}s%{public}s%{public}s",
594594
slot.index, oldestTimeslice.timeslice.value, copy ? "with copy" : "", copy && consume ? " and " : "", consume ? "with consume" : "");
595-
auto forwardedParts = DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copy, consume);
595+
auto forwardedParts = DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copy, consume);
596596

597597
for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
598598
if (forwardedParts[fi].Size() == 0) {

Framework/Core/src/DataProcessingHelpers.cxx

Lines changed: 107 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -228,102 +228,128 @@ TransitionHandlingState DataProcessingHelpers::updateStateTransition(ServiceRegi
228228
}
229229
}
230230

231-
auto DataProcessingHelpers::routeForwardedMessages(FairMQDeviceProxy& proxy,
232-
std::vector<MessageSet>& currentSetOfInputs,
233-
const bool copyByDefault, bool consume) -> std::vector<fair::mq::Parts>
231+
void DataProcessingHelpers::routeForwardedMessages(FairMQDeviceProxy& proxy, std::span<fair::mq::MessagePtr>& messages, std::vector<fair::mq::Parts>& forwardedParts,
232+
const bool copyByDefault, bool consume)
234233
{
235-
// we collect all messages per forward in a map and send them together
236-
std::vector<fair::mq::Parts> forwardedParts;
237-
forwardedParts.resize(proxy.getNumForwards());
238-
std::vector<ChannelIndex> forwardingChoices{};
239234
O2_SIGNPOST_ID_GENERATE(sid, forwarding);
235+
std::vector<ChannelIndex> forwardingChoices{};
236+
size_t pi = 0;
237+
while (pi < messages.size()) {
238+
auto& header = messages[pi];
240239

241-
for (size_t ii = 0, ie = currentSetOfInputs.size(); ii < ie; ++ii) {
242-
auto& messageSet = currentSetOfInputs[ii];
240+
// If is now possible that the record is not complete when
241+
// we forward it, because of a custom completion policy.
242+
// this means that we need to skip the empty entries in the
243+
// record for being forwarded.
244+
if (header->GetData() == nullptr) {
245+
pi += 2;
246+
continue;
247+
}
248+
auto dih = o2::header::get<DomainInfoHeader*>(header->GetData());
249+
if (dih) {
250+
pi += 2;
251+
continue;
252+
}
253+
auto sih = o2::header::get<SourceInfoHeader*>(header->GetData());
254+
if (sih) {
255+
pi += 2;
256+
continue;
257+
}
243258

244-
for (size_t pi = 0; pi < messageSet.size(); ++pi) {
245-
auto& header = messageSet.header(pi);
259+
auto dph = o2::header::get<DataProcessingHeader*>(header->GetData());
260+
auto dh = o2::header::get<o2::header::DataHeader*>(header->GetData());
246261

247-
// If is now possible that the record is not complete when
248-
// we forward it, because of a custom completion policy.
249-
// this means that we need to skip the empty entries in the
250-
// record for being forwarded.
251-
if (header->GetData() == nullptr) {
252-
continue;
253-
}
254-
auto dih = o2::header::get<DomainInfoHeader*>(header->GetData());
255-
if (dih) {
256-
continue;
257-
}
258-
auto sih = o2::header::get<SourceInfoHeader*>(header->GetData());
259-
if (sih) {
260-
continue;
261-
}
262+
if (dph == nullptr || dh == nullptr) {
263+
// Complain only if this is not an out-of-band message
264+
LOGP(error, "Data is missing {}{}{}",
265+
dph ? "DataProcessingHeader" : "", dph || dh ? "and" : "", dh ? "DataHeader" : "");
266+
pi += 2;
267+
continue;
268+
}
262269

263-
auto dph = o2::header::get<DataProcessingHeader*>(header->GetData());
264-
auto dh = o2::header::get<o2::header::DataHeader*>(header->GetData());
270+
// At least one payload.
271+
auto& payload = messages[pi + 1];
272+
// Calculate the number of messages which should be handled together
273+
// all in one go.
274+
size_t numberOfMessages = 0;
275+
if (dh->splitPayloadParts > 0 && dh->splitPayloadParts == dh->splitPayloadIndex) {
276+
// Sequence of (header, payload[0], ... , payload[splitPayloadParts - 1]) pairs belonging together.
277+
numberOfMessages = dh->splitPayloadParts + 1; // one is for the header
278+
} else {
279+
// Sequence of splitPayloadParts (header, payload) pairs belonging together.
280+
// In case splitPayloadParts = 0, we consider this as a single message pair
281+
numberOfMessages = (dh->splitPayloadParts > 0 ? dh->splitPayloadParts : 1) * 2;
282+
}
265283

266-
if (dph == nullptr || dh == nullptr) {
267-
// Complain only if this is not an out-of-band message
268-
LOGP(error, "Data is missing {}{}{}",
269-
dph ? "DataProcessingHeader" : "", dph || dh ? "and" : "", dh ? "DataHeader" : "");
270-
continue;
271-
}
284+
if (payload.get() == nullptr && consume == true) {
285+
// If the payload is not there, it means we already
286+
// processed it with ConsumeExisiting. Therefore we
287+
// need to do something only if this is the last consume.
288+
header.reset(nullptr);
289+
pi += numberOfMessages;
290+
continue;
291+
}
272292

273-
auto& payload = messageSet.payload(pi);
293+
// We need to find the forward route only for the first
294+
// part of a split payload. All the others will use the same.
295+
// Therefore, we reset and recompute the forwarding choice:
296+
//
297+
// - If this is the first payload of a [header0][payload0][header0][payload1]... sequence,
298+
// which is actually always created and handled together. Notice that in this
299+
// case we have splitPayloadParts == splitPayloadIndex
300+
// - If this is the first payload of a [header0][payload0][header1][payload1]... sequence
301+
// belonging to the same multipart message (and therefore we are guaranteed that they
302+
// need to be routed together).
303+
// - If the message is not a multipart (splitPayloadParts 0) or has only one part
304+
// - If it's a message of the kind [header0][payload1][payload2][payload3]... and therefore
305+
// we will already use the same choice in the for loop below.
306+
//
274307

275-
if (payload.get() == nullptr && consume == true) {
276-
// If the payload is not there, it means we already
277-
// processed it with ConsumeExisiting. Therefore we
278-
// need to do something only if this is the last consume.
279-
header.reset(nullptr);
280-
continue;
281-
}
308+
forwardingChoices.clear();
309+
proxy.getMatchingForwardChannelIndexes(forwardingChoices, *dh, dph->startTime);
282310

283-
// We need to find the forward route only for the first
284-
// part of a split payload. All the others will use the same.
285-
// Therefore, we reset and recompute the forwarding choice:
286-
//
287-
// - If this is the first payload of a [header0][payload0][header0][payload1] sequence,
288-
// which is actually always created and handled together
289-
// - If the message is not a multipart (splitPayloadParts 0) or has only one part
290-
// - If it's a message of the kind [header0][payload1][payload2][payload3]... and therefore
291-
// we will already use the same choice in the for loop below.
292-
if (dh->splitPayloadIndex == 0 || dh->splitPayloadParts <= 1 || messageSet.getNumberOfPayloads(pi) > 0) {
293-
forwardingChoices.clear();
294-
proxy.getMatchingForwardChannelIndexes(forwardingChoices, *dh, dph->startTime);
295-
}
311+
if (forwardingChoices.empty()) {
312+
// Nothing to forward go to the next messageset
313+
pi += numberOfMessages;
314+
continue;
315+
}
296316

297-
if (forwardingChoices.empty()) {
298-
// Nothing to forward go to the next messageset
299-
continue;
300-
}
317+
// In case of more than one forward route, we need to copy the message.
318+
// This will eventually use the same memory if running with the same backend.
319+
if (copyByDefault || forwardingChoices.size() > 1) {
320+
for (auto& choice : forwardingChoices) {
321+
O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding a copy of %{public}s to route %d.",
322+
fmt::format("{}/{}/{}@timeslice:{} tfCounter:{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification, dph->startTime, dh->tfCounter).c_str(), choice.value);
301323

302-
// In case of more than one forward route, we need to copy the message.
303-
// This will eventually use the same memory if running with the same backend.
304-
if (copyByDefault || forwardingChoices.size() > 1) {
305-
for (auto& choice : forwardingChoices) {
306-
auto&& newHeader = header->GetTransport()->CreateMessage();
307-
O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding a copy of %{public}s to route %d.",
308-
fmt::format("{}/{}/{}@timeslice:{} tfCounter:{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification, dph->startTime, dh->tfCounter).c_str(), choice.value);
309-
newHeader->Copy(*header);
310-
forwardedParts[choice.value].AddPart(std::move(newHeader));
311-
312-
for (size_t payloadIndex = 0; payloadIndex < messageSet.getNumberOfPayloads(pi); ++payloadIndex) {
313-
auto&& newPayload = header->GetTransport()->CreateMessage();
314-
newPayload->Copy(*messageSet.payload(pi, payloadIndex));
315-
forwardedParts[choice.value].AddPart(std::move(newPayload));
316-
}
317-
}
318-
} else {
319-
O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding %{public}s to route %d.",
320-
fmt::format("{}/{}/{}@timeslice:{} tfCounter:{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification, dph->startTime, dh->tfCounter).c_str(), forwardingChoices.back().value);
321-
forwardedParts[forwardingChoices.back().value].AddPart(std::move(messageSet.header(pi)));
322-
for (size_t payloadIndex = 0; payloadIndex < messageSet.getNumberOfPayloads(pi); ++payloadIndex) {
323-
forwardedParts[forwardingChoices.back().value].AddPart(std::move(messageSet.payload(pi, payloadIndex)));
324+
for (size_t ppi = pi; ppi < pi + numberOfMessages; ++ppi) {
325+
auto&& newMsg = header->GetTransport()->CreateMessage();
326+
newMsg->Copy(*messages[ppi]);
327+
forwardedParts[choice.value].AddPart(std::move(newMsg));
324328
}
325329
}
330+
} else {
331+
O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding %{public}s to route %d.",
332+
fmt::format("{}/{}/{}@timeslice:{} tfCounter:{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification, dph->startTime, dh->tfCounter).c_str(), forwardingChoices.back().value);
333+
for (size_t ppi = pi; ppi < pi + numberOfMessages; ++ppi) {
334+
forwardedParts[forwardingChoices.back().value].AddPart(std::move(messages[ppi]));
335+
}
326336
}
337+
pi += numberOfMessages;
338+
}
339+
}
340+
341+
auto DataProcessingHelpers::routeForwardedMessageSet(FairMQDeviceProxy& proxy,
342+
std::vector<MessageSet>& currentSetOfInputs,
343+
const bool copyByDefault, bool consume) -> std::vector<fair::mq::Parts>
344+
{
345+
// we collect all messages per forward in a map and send them together
346+
std::vector<fair::mq::Parts> forwardedParts;
347+
forwardedParts.resize(proxy.getNumForwards());
348+
std::vector<ChannelIndex> forwardingChoices{};
349+
350+
for (size_t ii = 0, ie = currentSetOfInputs.size(); ii < ie; ++ii) {
351+
auto span = std::span<fair::mq::MessagePtr>(currentSetOfInputs[ii].messages);
352+
routeForwardedMessages(proxy, span, forwardedParts, copyByDefault, consume);
327353
}
328354
return forwardedParts;
329355
};

Framework/Core/test/test_ForwardInputs.cxx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ TEST_CASE("ForwardInputsEmpty")
4545

4646
std::vector<MessageSet> currentSetOfInputs;
4747

48-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
48+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
4949
REQUIRE(result.empty());
5050
}
5151

@@ -95,7 +95,7 @@ TEST_CASE("ForwardInputsSingleMessageSingleRoute")
9595
REQUIRE(messageSet.size() == 1);
9696
currentSetOfInputs.emplace_back(std::move(messageSet));
9797

98-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
98+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
9999
REQUIRE(result.size() == 1); // One route
100100
REQUIRE(result[0].Size() == 2); // Two messages for that route
101101
}
@@ -146,7 +146,7 @@ TEST_CASE("ForwardInputsSingleMessageSingleRouteNoConsume")
146146
REQUIRE(messageSet.size() == 1);
147147
currentSetOfInputs.emplace_back(std::move(messageSet));
148148

149-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, true);
149+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, true);
150150
REQUIRE(result.size() == 1);
151151
REQUIRE(result[0].Size() == 0); // Because there is a nullptr, we do not forward this as it was already consumed.
152152
}
@@ -201,8 +201,7 @@ TEST_CASE("ForwardInputsSingleMessageSingleRouteAtEOS")
201201
REQUIRE(messageSet.size() == 1);
202202
currentSetOfInputs.emplace_back(std::move(messageSet));
203203

204-
205-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
204+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
206205
REQUIRE(result.size() == 1); // One route
207206
REQUIRE(result[0].Size() == 0); // FIXME: this is an actual error. It should be 2. However it cannot really happen.
208207
// Correct behavior below:
@@ -260,7 +259,7 @@ TEST_CASE("ForwardInputsSingleMessageSingleRouteWithOldestPossible")
260259
REQUIRE(messageSet.size() == 1);
261260
currentSetOfInputs.emplace_back(std::move(messageSet));
262261

263-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
262+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
264263
REQUIRE(result.size() == 1); // One route
265264
REQUIRE(result[0].Size() == 0); // FIXME: this is actually wrong
266265
// FIXME: actually correct behavior below
@@ -325,7 +324,7 @@ TEST_CASE("ForwardInputsSingleMessageMultipleRoutes")
325324
REQUIRE(messageSet.size() == 1);
326325
currentSetOfInputs.emplace_back(std::move(messageSet));
327326

328-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
327+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
329328
REQUIRE(result.size() == 2); // Two routes
330329
REQUIRE(result[0].Size() == 2); // Two messages per route
331330
REQUIRE(result[1].Size() == 0); // Only the first DPL matched channel matters
@@ -388,7 +387,7 @@ TEST_CASE("ForwardInputsSingleMessageMultipleRoutesExternals")
388387
REQUIRE(messageSet.size() == 1);
389388
currentSetOfInputs.emplace_back(std::move(messageSet));
390389

391-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
390+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
392391
REQUIRE(result.size() == 2); // Two routes
393392
REQUIRE(result[0].Size() == 2); // With external matching channels, we need to copy and then forward
394393
REQUIRE(result[1].Size() == 2); //
@@ -466,7 +465,7 @@ TEST_CASE("ForwardInputsMultiMessageMultipleRoutes")
466465
currentSetOfInputs.emplace_back(std::move(messageSet2));
467466
REQUIRE(currentSetOfInputs.size() == 2);
468467

469-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
468+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
470469
REQUIRE(result.size() == 2); // Two routes
471470
REQUIRE(result[0].Size() == 2); //
472471
REQUIRE(result[1].Size() == 2); //
@@ -529,7 +528,7 @@ TEST_CASE("ForwardInputsSingleMessageMultipleRoutesOnlyOneMatches")
529528
REQUIRE(messageSet.size() == 1);
530529
currentSetOfInputs.emplace_back(std::move(messageSet));
531530

532-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
531+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
533532
REQUIRE(result.size() == 2); // Two routes
534533
REQUIRE(result[0].Size() == 0); // Two messages per route
535534
REQUIRE(result[1].Size() == 2); // Two messages per route
@@ -541,7 +540,7 @@ TEST_CASE("ForwardInputsSplitPayload")
541540
dh.dataOrigin = "TST";
542541
dh.dataDescription = "A";
543542
dh.subSpecification = 0;
544-
dh.splitPayloadIndex = 0;
543+
dh.splitPayloadIndex = 2;
545544
dh.splitPayloadParts = 2;
546545

547546
o2::header::DataHeader dh2;
@@ -611,7 +610,7 @@ TEST_CASE("ForwardInputsSplitPayload")
611610
REQUIRE(messageSet.size() == 2);
612611
currentSetOfInputs.emplace_back(std::move(messageSet));
613612

614-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
613+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
615614
REQUIRE(result.size() == 2); // Two routes
616615
CHECK(result[0].Size() == 2); // No messages on this route
617616
CHECK(result[1].Size() == 3);
@@ -657,7 +656,7 @@ TEST_CASE("ForwardInputEOSSingleRoute")
657656
REQUIRE(messageSet.size() == 1);
658657
currentSetOfInputs.emplace_back(std::move(messageSet));
659658

660-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
659+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
661660
REQUIRE(result.size() == 1); // One route
662661
REQUIRE(result[0].Size() == 0); // Oldest possible timeframe should not be forwarded
663662
}
@@ -702,7 +701,7 @@ TEST_CASE("ForwardInputOldestPossibleSingleRoute")
702701
REQUIRE(messageSet.size() == 1);
703702
currentSetOfInputs.emplace_back(std::move(messageSet));
704703

705-
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, currentSetOfInputs, copyByDefault, consume);
704+
auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume);
706705
REQUIRE(result.size() == 1); // One route
707706
REQUIRE(result[0].Size() == 0); // Oldest possible timeframe should not be forwarded
708707
}

0 commit comments

Comments
 (0)