Project
Loading...
Searching...
No Matches
benchmark_DataRelayer.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11#include <benchmark/benchmark.h>
12
13#include "Headers/DataHeader.h"
14#include "Headers/Stack.h"
25#include <Monitoring/Monitoring.h>
26#include <fairmq/TransportFactory.h>
27#include <cstring>
28#include <cstdio>
29#include <iterator>
30#include <vector>
31#include <uv.h>
32
33using Monitoring = o2::monitoring::Monitoring;
34using namespace o2::framework;
38
51
53 {
54 using MetricSpec = DataProcessingStats::MetricSpec;
55 int quickUpdateInterval = 1;
56 std::vector<MetricSpec> specs{
57 MetricSpec{.name = "malformed_inputs", .metricId = static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), .minPublishInterval = quickUpdateInterval},
58 MetricSpec{.name = "dropped_computations", .metricId = static_cast<short>(ProcessingStatsId::DROPPED_COMPUTATIONS), .minPublishInterval = quickUpdateInterval},
59 MetricSpec{.name = "dropped_incoming_messages", .metricId = static_cast<short>(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), .minPublishInterval = quickUpdateInterval},
60 MetricSpec{.name = "relayed_messages", .metricId = static_cast<short>(ProcessingStatsId::RELAYED_MESSAGES), .minPublishInterval = quickUpdateInterval}};
61 for (auto& spec : specs) {
63 }
64
66 r.registerService(ServiceRegistryHelpers::handleForService<Monitoring>(&monitoring));
67 r.registerService(ServiceRegistryHelpers::handleForService<DataProcessingStates>(&states));
68 r.registerService(ServiceRegistryHelpers::handleForService<DataProcessingStats>(&stats));
69 r.registerService(ServiceRegistryHelpers::handleForService<DriverConfig const>(&driverConfig));
70 r.registerService(ServiceRegistryHelpers::handleForService<DeviceState>(&deviceState));
71 return r;
72 }
73};
74
75// a simple benchmark of the contribution of the pure message creation
76// this was important when the benchmarks below included the message
77// creation inside the benchmark loop, its somewhat obsolete now but
78// we keep it for reference
79static void BM_RelayMessageCreation(benchmark::State& state)
80{
81 DataHeader dh;
82 dh.dataDescription = "CLUSTERS";
83 dh.dataOrigin = "TPC";
84 dh.subSpecification = 0;
85
86 DataProcessingHeader dph{0, 1};
87 Stack stack{dh, dph};
88 auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
89
90 for (auto _ : state) {
91 fair::mq::MessagePtr header = transport->CreateMessage(stack.size());
92 fair::mq::MessagePtr payload = transport->CreateMessage(1000);
93 memcpy(header->GetData(), stack.data(), stack.size());
94 }
95}
96
97BENCHMARK(BM_RelayMessageCreation);
98
99// A simple test where an input is provided
100// and the subsequent InputRecord is immediately requested.
101static void BM_RelaySingleSlot(benchmark::State& state)
102{
103 BenchmarkServices services;
104 InputSpec spec{"clusters", "TPC", "CLUSTERS"};
105
106 std::vector<InputRoute> inputs = {
107 InputRoute{spec, 0, "Fake", 0}};
108
109 std::vector<ForwardRoute> forwards;
110 std::vector<InputChannelInfo> infos{1};
111 TimesliceIndex index{1, infos};
113 DataRelayer relayer(policy, inputs, index, services.ref(), -1);
114 relayer.setPipelineLength(4);
115
116 // Let's create a dummy O2 Message with two headers in the stack:
117 // - DataHeader matching the one provided in the input
118 DataHeader dh;
119 dh.dataDescription = "CLUSTERS";
120 dh.dataOrigin = "TPC";
121 dh.subSpecification = 0;
122
123 DataProcessingHeader dph{0, 1};
124 Stack stack{dh, dph};
125 auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
126 // we are creating the inflight messages once outside the benchmark
127 // loop and make sure that they are moved back to the original vector
128 // when processed by the relayer
129 std::vector<fair::mq::MessagePtr> inflightMessages;
130 inflightMessages.emplace_back(transport->CreateMessage(stack.size()));
131 inflightMessages.emplace_back(transport->CreateMessage(1000));
132 memcpy(inflightMessages[0]->GetData(), stack.data(), stack.size());
133
134 DataRelayer::InputInfo fakeInfo{0, inflightMessages.size(), DataRelayer::InputType::Data, {ChannelIndex::INVALID}};
135 for (auto _ : state) {
136 relayer.relay(inflightMessages[0]->GetData(), inflightMessages.data(), fakeInfo, inflightMessages.size());
137 std::vector<RecordAction> ready;
138 relayer.getReadyToProcess(ready);
139 assert(ready.size() == 1);
140 assert(ready[0].slot.index == 0);
141 assert(ready[0].op == CompletionPolicy::CompletionOp::Consume);
142 auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot);
143 assert((result | count_inputs{}) == 1);
144 assert((result[0] | count_parts{}) == 1);
145 inflightMessages.assign(std::make_move_iterator(result[0].begin()),
146 std::make_move_iterator(result[0].end()));
147 }
148}
149
150BENCHMARK(BM_RelaySingleSlot);
151
152// This one will simulate a single input.
153static void BM_RelayMultipleSlots(benchmark::State& state)
154{
155 BenchmarkServices services;
156 InputSpec spec{"clusters", "TPC", "CLUSTERS"};
157
158 std::vector<InputRoute> inputs = {
159 InputRoute{spec, 0, "Fake", 0}};
160
161 std::vector<ForwardRoute> forwards;
162 std::vector<InputChannelInfo> infos{1};
163 TimesliceIndex index{1, infos};
164
166 DataRelayer relayer(policy, inputs, index, services.ref(), -1);
167 relayer.setPipelineLength(4);
168
169 // Let's create a dummy O2 Message with two headers in the stack:
170 // - DataHeader matching the one provided in the input
171 DataHeader dh;
172 dh.dataDescription = "CLUSTERS";
173 dh.dataOrigin = "TPC";
174 dh.subSpecification = 0;
175
176 auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
177 size_t timeslice = 0;
178
179 DataProcessingHeader dph{timeslice, 1};
180 Stack placeholder{dh, dph};
181
182 // we are creating the inflight messages once outside the benchmark
183 // loop and make sure that they are moved back to the original vector
184 // when processed by the relayer
185 std::vector<fair::mq::MessagePtr> inflightMessages;
186 inflightMessages.emplace_back(transport->CreateMessage(placeholder.size()));
187 inflightMessages.emplace_back(transport->CreateMessage(1000));
188
189 for (auto _ : state) {
190 Stack stack{dh, DataProcessingHeader{timeslice++, 1}};
191 memcpy(inflightMessages[0]->GetData(), stack.data(), stack.size());
192
193 DataRelayer::InputInfo fakeInfo{0, inflightMessages.size(), DataRelayer::InputType::Data, {ChannelIndex::INVALID}};
194 relayer.relay(inflightMessages[0]->GetData(), inflightMessages.data(), fakeInfo, inflightMessages.size());
195 std::vector<RecordAction> ready;
196 relayer.getReadyToProcess(ready);
197 assert(ready.size() == 1);
198 assert(ready[0].op == CompletionPolicy::CompletionOp::Consume);
199 auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot);
200 assert((result | count_inputs{}) == 1);
201 assert((result[0] | count_parts{}) == 1);
202 inflightMessages.assign(std::make_move_iterator(result[0].begin()),
203 std::make_move_iterator(result[0].end()));
204 }
205}
206
207BENCHMARK(BM_RelayMultipleSlots);
208
210static void BM_RelayMultipleRoutes(benchmark::State& state)
211{
212 BenchmarkServices services;
213 InputSpec spec1{"clusters", "TPC", "CLUSTERS"};
214 InputSpec spec2{"tracks", "TPC", "TRACKS"};
215
216 std::vector<InputRoute> inputs = {
217 InputRoute{spec1, 0, "Fake1", 0},
218 InputRoute{spec2, 1, "Fake2", 0}};
219
220 std::vector<ForwardRoute> forwards;
221 std::vector<InputChannelInfo> infos{1};
222 TimesliceIndex index{1, infos};
223
225 DataRelayer relayer(policy, inputs, index, services.ref(), -1);
226 relayer.setPipelineLength(4);
227
228 // Let's create a dummy O2 Message with two headers in the stack:
229 // - DataHeader matching the one provided in the input
230 DataHeader dh1;
231 dh1.dataDescription = "CLUSTERS";
232 dh1.dataOrigin = "TPC";
233 dh1.subSpecification = 0;
234
235 DataHeader dh2;
236 dh2.dataDescription = "TRACKS";
237 dh2.dataOrigin = "TPC";
238 dh2.subSpecification = 0;
239
240 auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
241 size_t timeslice = 0;
242
243 DataProcessingHeader dph1{timeslice, 1};
244 Stack stack1{dh1, dph1};
245
246 std::vector<fair::mq::MessagePtr> inflightMessages;
247 inflightMessages.emplace_back(transport->CreateMessage(stack1.size()));
248 inflightMessages.emplace_back(transport->CreateMessage(1000));
249
250 memcpy(inflightMessages[0]->GetData(), stack1.data(), stack1.size());
251
252 DataProcessingHeader dph2{timeslice, 1};
253 Stack stack2{dh2, dph2};
254
255 inflightMessages.emplace_back(transport->CreateMessage(stack2.size()));
256 inflightMessages.emplace_back(transport->CreateMessage(1000));
257
258 memcpy(inflightMessages[2]->GetData(), stack2.data(), stack2.size());
259
260 for (auto _ : state) {
261 DataRelayer::InputInfo fakeInfo{0, inflightMessages.size(), DataRelayer::InputType::Data, {ChannelIndex::INVALID}};
262 relayer.relay(inflightMessages[0]->GetData(), &inflightMessages[0], fakeInfo, 2);
263 std::vector<RecordAction> ready;
264 relayer.getReadyToProcess(ready);
265 assert(ready.size() == 1);
266 assert(ready[0].op == CompletionPolicy::CompletionOp::Consume);
267
268 DataRelayer::InputInfo fakeInfo2{0, inflightMessages.size(), DataRelayer::InputType::Data, {ChannelIndex::INVALID}};
269 relayer.relay(inflightMessages[2]->GetData(), &inflightMessages[2], fakeInfo2, 2);
270 ready.clear();
271 relayer.getReadyToProcess(ready);
272 assert(ready.size() == 1);
273 assert(ready[0].op == CompletionPolicy::CompletionOp::Consume);
274 auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot);
275 assert((result | count_inputs{}) == 2);
276 assert((result[0] | count_parts{}) == 1);
277 assert((result[1] | count_parts{}) == 1);
278 inflightMessages.assign(std::make_move_iterator(result[0].begin()),
279 std::make_move_iterator(result[0].end()));
280 inflightMessages.insert(inflightMessages.end(),
281 std::make_move_iterator(result[1].begin()),
282 std::make_move_iterator(result[1].end()));
283 }
284}
285
286BENCHMARK(BM_RelayMultipleRoutes);
287
289static void BM_RelaySplitParts(benchmark::State& state)
290{
291 BenchmarkServices services;
292 InputSpec spec1{"clusters", "TPC", "CLUSTERS"};
293
294 std::vector<InputRoute> inputs = {
295 InputRoute{spec1, 0, "Fake1", 0},
296 };
297
298 std::vector<ForwardRoute> forwards;
299 std::vector<InputChannelInfo> infos{1};
300 TimesliceIndex index{1, infos};
301
303 DataRelayer relayer(policy, inputs, index, services.ref(), -1);
304 relayer.setPipelineLength(4);
305
306 // Let's create a dummy O2 Message with two headers in the stack:
307 // - DataHeader matching the one provided in the input
308 DataHeader dh;
309 dh.dataDescription = "CLUSTERS";
310 dh.dataOrigin = "TPC";
311 dh.subSpecification = 0;
312 dh.payloadSize = 100;
313
314 auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
315 size_t timeslice = 0;
316 const int nSplitParts = state.range(0);
317
318 std::vector<std::unique_ptr<fair::mq::Message>> inflightMessages;
319 inflightMessages.reserve(2 * nSplitParts);
320
321 for (size_t i = 0; i < nSplitParts; ++i) {
322 DataProcessingHeader dph{timeslice, 1};
323 dh.splitPayloadIndex = i;
324 dh.splitPayloadParts = nSplitParts;
325 Stack stack{dh, dph};
326
327 fair::mq::MessagePtr header = transport->CreateMessage(stack.size());
328 fair::mq::MessagePtr payload = transport->CreateMessage(dh.payloadSize);
329
330 memcpy(header->GetData(), stack.data(), stack.size());
331 inflightMessages.emplace_back(std::move(header));
332 inflightMessages.emplace_back(std::move(payload));
333 }
334
335 DataRelayer::InputInfo fakeInfo{0, inflightMessages.size(), DataRelayer::InputType::Data, {ChannelIndex::INVALID}};
336 for (auto _ : state) {
337 relayer.relay(inflightMessages[0]->GetData(), inflightMessages.data(), fakeInfo, inflightMessages.size());
338 std::vector<RecordAction> ready;
339 relayer.getReadyToProcess(ready);
340 assert(ready.size() == 1);
341 assert(ready[0].op == CompletionPolicy::CompletionOp::Consume);
342 auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot);
343 inflightMessages.assign(std::make_move_iterator(result[0].begin()),
344 std::make_move_iterator(result[0].end()));
345 }
346}
347
348BENCHMARK(BM_RelaySplitParts)->Arg(10)->Arg(100)->Arg(1000);
349
350static void BM_RelayMultiplePayloads(benchmark::State& state)
351{
352 BenchmarkServices services;
353 InputSpec spec1{"clusters", "TPC", "CLUSTERS"};
354
355 std::vector<InputRoute> inputs = {
356 InputRoute{spec1, 0, "Fake1", 0},
357 };
358
359 std::vector<ForwardRoute> forwards;
360 std::vector<InputChannelInfo> infos{1};
361 TimesliceIndex index{1, infos};
362
364 DataRelayer relayer(policy, inputs, index, services.ref(), -1);
365 relayer.setPipelineLength(4);
366
367 // DataHeader matching the one provided in the input
368 DataHeader dh;
369 dh.dataDescription = "CLUSTERS";
370 dh.dataOrigin = "TPC";
371 dh.subSpecification = 0;
372 dh.payloadSize = 100;
373
374 auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
375 size_t timeslice = 0;
376 const int nPayloads = state.range(0);
377 std::vector<std::unique_ptr<fair::mq::Message>> inflightMessages;
378 inflightMessages.reserve(nPayloads + 1);
379
380 DataProcessingHeader dph{timeslice, 1};
381 dh.splitPayloadIndex = nPayloads;
382 dh.splitPayloadParts = nPayloads;
383 Stack stack{dh, dph};
384 fair::mq::MessagePtr header = transport->CreateMessage(stack.size());
385 memcpy(header->GetData(), stack.data(), stack.size());
386 inflightMessages.emplace_back(std::move(header));
387 for (size_t i = 0; i < nPayloads; ++i) {
388 inflightMessages.emplace_back(transport->CreateMessage(dh.payloadSize));
389 }
390
391 DataRelayer::InputInfo fakeInfo{0, inflightMessages.size(), DataRelayer::InputType::Data, {ChannelIndex::INVALID}};
392 for (auto _ : state) {
393 relayer.relay(inflightMessages[0]->GetData(), inflightMessages.data(), fakeInfo, inflightMessages.size(), nPayloads);
394 std::vector<RecordAction> ready;
395 relayer.getReadyToProcess(ready);
396 assert(ready.size() == 1);
397 assert(ready[0].op == CompletionPolicy::CompletionOp::Consume);
398 auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot);
399 inflightMessages.assign(std::make_move_iterator(result[0].begin()),
400 std::make_move_iterator(result[0].end()));
401 }
402}
403
404BENCHMARK(BM_RelayMultiplePayloads)->Arg(10)->Arg(100)->Arg(1000);
405
406// Every benchmark above uses one or two inputs, which is exactly the regime
407// where per-input storage costs nothing to speak of. Sweep the number of inputs
408// so a change to how a slot holds its messages is visible where it matters.
409//
410// Note this is the only benchmark here using consumeWhenAll, which needs the
411// TimesliceIndex from the registry (CompletionPolicyHelpers.cxx). The others use
412// consumeWhenAny and never look it up, which is why BenchmarkServices does not
413// register it and why it has to be registered here.
414static void BM_RelayManyInputs(benchmark::State& state)
415{
416 BenchmarkServices services;
417 size_t const nInputs = state.range(0);
418
419 std::vector<InputSpec> specs;
420 std::vector<InputRoute> inputs;
421 std::vector<DataHeader> prototypes;
422 specs.reserve(nInputs);
423 for (size_t i = 0; i < nInputs; ++i) {
424 char description[16];
425 snprintf(description, sizeof(description), "DATA%03zu", i);
428 specs.emplace_back(InputSpec{"in", "TST", desc});
429 DataHeader dh;
430 dh.dataOrigin = "TST";
431 dh.dataDescription = desc;
432 dh.subSpecification = 0;
433 dh.splitPayloadIndex = 0;
434 dh.splitPayloadParts = 1;
435 dh.payloadSize = 100;
436 prototypes.push_back(dh);
437 }
438 for (size_t i = 0; i < nInputs; ++i) {
439 inputs.emplace_back(InputRoute{specs[i], i, "Fake", 0});
440 }
441
442 std::vector<InputChannelInfo> infos{1};
443 TimesliceIndex index{1, infos};
444 auto ref = services.ref();
445 ref.registerService(ServiceRegistryHelpers::handleForService<TimesliceIndex>(&index));
446
448 DataRelayer relayer(policy, inputs, index, ref, -1);
449 relayer.setPipelineLength(1);
450
451 auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
452
453 // One message pair per input, recycled through the relayer every iteration.
454 std::vector<fair::mq::MessagePtr> inflight;
455 inflight.reserve(2 * nInputs);
456 for (size_t i = 0; i < nInputs; ++i) {
457 Stack stack{prototypes[i], DataProcessingHeader{0, 1}};
458 fair::mq::MessagePtr header = transport->CreateMessage(stack.size());
459 memcpy(header->GetData(), stack.data(), stack.size());
460 inflight.emplace_back(std::move(header));
461 inflight.emplace_back(transport->CreateMessage(prototypes[i].payloadSize));
462 }
463
464 for (auto _ : state) {
465 for (size_t i = 0; i < nInputs; ++i) {
466 DataRelayer::InputInfo info{0, 2, DataRelayer::InputType::Data, {ChannelIndex::INVALID}};
467 relayer.relay(inflight[2 * i]->GetData(), &inflight[2 * i], info, 2);
468 }
469 std::vector<RecordAction> ready;
470 relayer.getReadyToProcess(ready);
471 assert(ready.size() == 1);
472 auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot);
473 inflight.clear();
474 for (size_t i = 0; i < nInputs; ++i) {
475 for (auto& msg : result[i]) {
476 inflight.emplace_back(std::move(msg));
477 }
478 }
479 }
480}
481
482BENCHMARK(BM_RelayManyInputs)->Arg(1)->Arg(8)->Arg(32)->Arg(128);
483
header::DataDescription description
benchmark::State & state
int32_t i
uint32_t op
uint32_t stack
Definition RawData.h:1
BENCHMARK_MAIN()
o2::monitoring::Monitoring Monitoring
BENCHMARK(BM_RelayMessageCreation)
void getReadyToProcess(std::vector< RecordAction > &completed)
void setPipelineLength(size_t s)
Tune the maximum number of in flight timeslices this can handle.
std::vector< std::vector< fair::mq::MessagePtr > > consumeAllInputsForTimeslice(TimesliceSlot id)
RelayChoice relay(void const *rawHeader, std::unique_ptr< fair::mq::Message > *messages, InputInfo const &info, size_t nMessages, size_t nPayloads=1, OnInsertionCallback onInsertion=nullptr, OnDropCallback onDrop=nullptr)
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint GLuint end
Definition glcorearb.h:469
GLuint index
Definition glcorearb.h:781
GLboolean r
Definition glcorearb.h:1233
GLuint * states
Definition glcorearb.h:4932
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
Enum< T >::Iterator begin(Enum< T >)
Definition Defs.h:156
const DriverConfig driverConfig
ServiceRegistryRef ref()
DataProcessingStats stats
static constexpr int INVALID
static CompletionPolicy consumeWhenAll(const char *name, CompletionPolicy::Matcher matcher)
Default Completion policy. When all the parts of a record have arrived, consume them.
static CompletionPolicy consumeWhenAny(const char *name, CompletionPolicy::Matcher matcher)
When any of the parts of the record have been received, consume them.
Helper struct to hold statistics about the data processing happening.
void registerMetric(MetricSpec const &spec)
Running state information of a given device.
Definition DeviceState.h:34
bool batch
Whether the driver was started in batch mode or not.
void registerService(ServiceTypeHash typeHash, void *service, ServiceKind kind, Salt salt, char const *name=nullptr, ServiceRegistry::SpecIndex specIndex=SpecIndex{-1}) const
static std::function< int64_t(int64_t base, int64_t offset)> defaultCPUTimeConfigurator(uv_loop_t *loop)
static std::function< void(int64_t &base, int64_t &offset)> defaultRealtimeBaseConfigurator(uint64_t offset, uv_loop_t *loop)
the main header struct
Definition DataHeader.h:620
SplitPayloadPartsType splitPayloadParts
Definition DataHeader.h:648
DataDescription dataDescription
Definition DataHeader.h:638
SubSpecificationType subSpecification
Definition DataHeader.h:658
PayloadSizeType payloadSize
Definition DataHeader.h:668
SplitPayloadIndexType splitPayloadIndex
Definition DataHeader.h:663
void runtimeInit(const char *string, short length=-1)
Definition DataHeader.h:261
a move-only header stack with serialized headers This is the flat buffer where all the headers in a m...
Definition Stack.h:33
uint64_t const void const *restrict const msg
Definition x9.h:153