1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #include "Profiling.hpp"
6
7 #include <armnn/BackendId.hpp>
8 #include <armnn/utility/Assert.hpp>
9
10 #include "JsonPrinter.hpp"
11
12 #if ARMNN_STREAMLINE_ENABLED
13 #include <streamline_annotate.h>
14 #endif
15
16 #include <algorithm>
17 #include <iomanip>
18 #include <iostream>
19 #include <fstream>
20 #include <map>
21 #include <stack>
22
23 namespace armnn
24 {
25
26 // Controls the amount of memory initially allocated to store profiling events.
27 // If chosen carefully, the profiling system will not make any additional allocations, thus minimizing its impact on
28 // measured times.
29 constexpr std::size_t g_ProfilingEventCountHint = 1024;
30
31 // Whether profiling reports should include the sequence of events together with their timings.
32 constexpr bool g_WriteProfilingEventSequence = true;
33
34 // Whether profiling reports should also report detailed information on events grouped by inference.
35 // This can spam the output stream, so use carefully (or adapt the code to just output information
36 // of interest).
37 constexpr bool g_AggregateProfilingEventsByInference = true;
38
39 // Whether a call to Profiler::AnalyzeEventsAndWriteResults() will be made when the Profiler is destroyed.
40 // It can be convenient for local tests.
41 constexpr bool g_WriteReportToStdOutOnProfilerDestruction = false;
42
FindMeasurement(const std::string & name,const Event * event)43 Measurement FindMeasurement(const std::string& name, const Event* event)
44 {
45
46 ARMNN_ASSERT(event != nullptr);
47
48 // Search though the measurements.
49 for (const auto& measurement : event->GetMeasurements())
50 {
51 if (measurement.m_Name == name)
52 {
53 // Measurement found.
54 return measurement;
55 }
56 }
57
58 // Measurement not found.
59 return Measurement{ "", 0.f, Measurement::Unit::TIME_MS };
60 }
61
FindKernelMeasurements(const Event * event)62 std::vector<Measurement> FindKernelMeasurements(const Event* event)
63 {
64 ARMNN_ASSERT(event != nullptr);
65
66 std::vector<Measurement> measurements;
67
68 // Search through the measurements.
69 for (const auto& measurement : event->GetMeasurements())
70 {
71 if (measurement.m_Name.rfind("OpenClKernelTimer", 0) == 0
72 || measurement.m_Name.rfind("NeonKernelTimer", 0) == 0)
73 {
74 // Measurement found.
75 measurements.push_back(measurement);
76 }
77 }
78
79 return measurements;
80 }
81
CalculateProfilingEventStats() const82 std::map<std::string, ProfilerImpl::ProfilingEventStats> ProfilerImpl::CalculateProfilingEventStats() const
83 {
84 std::map<std::string, ProfilingEventStats> nameToStatsMap;
85
86 for (const auto& event : m_EventSequence)
87 {
88 Measurement measurement = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME, event.get());
89
90 double durationMs = measurement.m_Value;
91 auto it = nameToStatsMap.find(event->GetName());
92 if (it != nameToStatsMap.end())
93 {
94 ProfilingEventStats& stats = it->second;
95 stats.m_TotalMs += durationMs;
96 stats.m_MinMs = std::min(stats.m_MinMs, durationMs);
97 stats.m_MaxMs = std::max(stats.m_MaxMs, durationMs);
98 ++stats.m_Count;
99 }
100 else
101 {
102 nameToStatsMap.emplace(event->GetName(), ProfilingEventStats{ durationMs, durationMs, durationMs, 1 });
103 }
104 }
105
106 return nameToStatsMap;
107 }
108
GetEventPtr(const Event * ptr)109 const Event* GetEventPtr(const Event* ptr) { return ptr;}
GetEventPtr(const std::unique_ptr<Event> & ptr)110 const Event* GetEventPtr(const std::unique_ptr<Event>& ptr) {return ptr.get(); }
111
112 template<typename ItertType>
AnalyzeEventSequenceAndWriteResults(ItertType first,ItertType last,std::ostream & outStream) const113 void ProfilerImpl::AnalyzeEventSequenceAndWriteResults(ItertType first, ItertType last, std::ostream& outStream) const
114 {
115 // Outputs event sequence, if needed.
116 if (g_WriteProfilingEventSequence)
117 {
118 // Makes sure timestamps are output with 6 decimals, and save old settings.
119 std::streamsize oldPrecision = outStream.precision();
120 outStream.precision(6);
121 std::ios_base::fmtflags oldFlags = outStream.flags();
122 outStream.setf(std::ios::fixed);
123 // Outputs fields.
124 outStream << "Event Sequence - Name | Duration (ms) | Start (ms) | Stop (ms) | Device" << std::endl;
125 for (auto event = first; event != last; ++event)
126 {
127 const Event* eventPtr = GetEventPtr((*event));
128 double startTimeMs = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME_START, eventPtr).m_Value;
129 double stopTimeMs = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME_STOP, eventPtr).m_Value;
130
131 // Find the WallClock measurement if there is one.
132 double durationMs = FindMeasurement(WallClockTimer::WALL_CLOCK_TIME, eventPtr).m_Value;
133 outStream << std::setw(50) << eventPtr->GetName() << " "
134 << std::setw(20) << durationMs
135 << std::setw(20) << startTimeMs
136 << std::setw(20) << stopTimeMs
137 << std::setw(20) << eventPtr->GetBackendId().Get()
138 << std::endl;
139 }
140 outStream << std::endl;
141 // Restores previous precision settings.
142 outStream.flags(oldFlags);
143 outStream.precision(oldPrecision);
144 }
145
146 // Aggregates results per event name.
147 std::map<std::string, ProfilingEventStats> nameToStatsMap = CalculateProfilingEventStats();
148
149 // Outputs aggregated stats.
150 outStream << "Event Stats - Name | Avg (ms) | Min (ms) | Max (ms) | Total (ms) | Count" << std::endl;
151 for (const auto& pair : nameToStatsMap)
152 {
153 const std::string& eventLabel = pair.first;
154 const ProfilingEventStats& eventStats = pair.second;
155 const double avgMs = eventStats.m_TotalMs / double(eventStats.m_Count);
156
157 outStream << "\t" << std::setw(50) << eventLabel << " " << std::setw(9) << avgMs << " "
158 << std::setw(9) << eventStats.m_MinMs << " " << std::setw(9) << eventStats.m_MaxMs << " "
159 << std::setw(9) << eventStats.m_TotalMs << " " << std::setw(9) << eventStats.m_Count << std::endl;
160 }
161 outStream << std::endl;
162 }
163
ProfilerImpl()164 ProfilerImpl::ProfilerImpl()
165 : m_ProfilingEnabled(false),
166 m_DetailsToStdOutMethod(ProfilingDetailsMethod::Undefined)
167 {
168 m_EventSequence.reserve(g_ProfilingEventCountHint);
169
170 #if ARMNN_STREAMLINE_ENABLED
171 // Initialises streamline annotations.
172 ANNOTATE_SETUP;
173 #endif
174 }
175
~ProfilerImpl()176 ProfilerImpl::~ProfilerImpl()
177 {
178 if (m_ProfilingEnabled)
179 {
180 if (g_WriteReportToStdOutOnProfilerDestruction)
181 {
182 Print(std::cout);
183 }
184 }
185
186 // Un-register this profiler from the current thread.
187 ProfilerManager::GetInstance().RegisterProfiler(nullptr);
188 }
189
IsProfilingEnabled()190 bool ProfilerImpl::IsProfilingEnabled()
191 {
192 return m_ProfilingEnabled;
193 }
194
EnableProfiling(bool enableProfiling)195 void ProfilerImpl::EnableProfiling(bool enableProfiling)
196 {
197 m_ProfilingEnabled = enableProfiling;
198 }
199
EnableNetworkDetailsToStdOut(ProfilingDetailsMethod details)200 void ProfilerImpl::EnableNetworkDetailsToStdOut(ProfilingDetailsMethod details)
201 {
202 m_DetailsToStdOutMethod = details;
203 }
204
BeginEvent(armnn::IProfiler * profiler,const BackendId & backendId,const std::string & label,std::vector<InstrumentPtr> && instruments,const Optional<arm::pipe::ProfilingGuid> & guid)205 Event* ProfilerImpl::BeginEvent(armnn::IProfiler* profiler,
206 const BackendId& backendId,
207 const std::string& label,
208 std::vector<InstrumentPtr>&& instruments,
209 const Optional<arm::pipe::ProfilingGuid>& guid)
210 {
211 Event* parent = m_Parents.empty() ? nullptr : m_Parents.top();
212 m_EventSequence.push_back(std::make_unique<Event>(label,
213 profiler,
214 parent,
215 backendId,
216 std::move(instruments),
217 guid));
218 Event* event = m_EventSequence.back().get();
219 event->Start();
220
221 #if ARMNN_STREAMLINE_ENABLED
222 ANNOTATE_CHANNEL_COLOR(uint32_t(m_Parents.size()), GetEventColor(backendId), label.c_str());
223 #endif
224
225 m_Parents.push(event);
226 return event;
227 }
228
EndEvent(Event * event)229 void ProfilerImpl::EndEvent(Event* event)
230 {
231 event->Stop();
232
233 ARMNN_ASSERT(!m_Parents.empty());
234 ARMNN_ASSERT(event == m_Parents.top());
235 m_Parents.pop();
236
237 Event* parent = m_Parents.empty() ? nullptr : m_Parents.top();
238 IgnoreUnused(parent);
239 ARMNN_ASSERT(event->GetParentEvent() == parent);
240
241 #if ARMNN_STREAMLINE_ENABLED
242 ANNOTATE_CHANNEL_END(uint32_t(m_Parents.size()));
243 #endif
244 }
245
CalcLevel(const Event * eventPtr)246 int CalcLevel(const Event* eventPtr)
247 {
248 int level = 0;
249 while (eventPtr != nullptr)
250 {
251 eventPtr = eventPtr->GetParentEvent();
252 level++;
253 }
254 return level;
255 }
256
PopulateParent(std::vector<const Event * > & outEvents,int & outBaseLevel,std::string parentName) const257 void ProfilerImpl::PopulateParent(std::vector<const Event*>& outEvents, int& outBaseLevel, std::string parentName) const
258 {
259 outEvents.reserve(m_EventSequence.size());
260 for (const auto& event : m_EventSequence)
261 {
262 const Event* eventPtrRaw = event.get();
263 if (eventPtrRaw->GetName() == parentName)
264 {
265 outBaseLevel = (outBaseLevel == -1) ? CalcLevel(eventPtrRaw) : outBaseLevel;
266 outEvents.push_back(eventPtrRaw);
267 }
268 }
269 }
270
PopulateDescendants(std::map<const Event *,std::vector<const Event * >> & outDescendantsMap) const271 void ProfilerImpl::PopulateDescendants(std::map<const Event*, std::vector<const Event*>>& outDescendantsMap) const
272 {
273 for (const auto& event : m_EventSequence)
274 {
275 const Event* eventPtrRaw = event.get();
276 const Event* parent = eventPtrRaw->GetParentEvent();
277
278 if (!parent)
279 {
280 continue;
281 }
282
283 auto it = outDescendantsMap.find(parent);
284 if (it == outDescendantsMap.end())
285 {
286 outDescendantsMap.emplace(parent, std::vector<const Event*>({ eventPtrRaw }));
287 }
288 else
289 {
290 it->second.push_back(eventPtrRaw);
291 }
292 }
293 }
294
ConfigureDetailsObject(JsonChildObject & detailsObject,std::string layerDetailsStr)295 void ConfigureDetailsObject(JsonChildObject& detailsObject,
296 std::string layerDetailsStr)
297 {
298 detailsObject.SetType(JsonObjectType::ExecObjectDesc);
299 detailsObject.SetAndParseDetails(layerDetailsStr);
300
301 }
302
ExtractJsonObjects(unsigned int inferenceIndex,const Event * parentEvent,JsonChildObject & parentObject,std::map<const Event *,std::vector<const Event * >> descendantsMap)303 void ExtractJsonObjects(unsigned int inferenceIndex,
304 const Event* parentEvent,
305 JsonChildObject& parentObject,
306 std::map<const Event*, std::vector<const Event*>> descendantsMap)
307 {
308 ARMNN_ASSERT(parentEvent);
309
310 // If profiling GUID is entered, process it
311 if (parentEvent->GetProfilingGuid().has_value())
312 {
313 arm::pipe::ProfilingGuid profilingGuid;
314 profilingGuid = parentEvent->GetProfilingGuid().value();
315 parentObject.SetGuid(profilingGuid);
316 }
317 std::vector<Measurement> instrumentMeasurements = parentEvent->GetMeasurements();
318 unsigned int childIdx = 0;
319 unsigned int numSkippedKernels = 0;
320 if (inferenceIndex > 0)
321 {
322 for (auto &i: parentEvent->GetInstruments())
323 {
324 if (i->HasKernelMeasurements())
325 {
326 numSkippedKernels = static_cast<unsigned int>(parentObject.m_Children.size() -
327 instrumentMeasurements.size());
328 childIdx = numSkippedKernels;
329 }
330 }
331 }
332
333 for (size_t measurementIndex = 0; measurementIndex < instrumentMeasurements.size(); ++measurementIndex, ++childIdx)
334 {
335 if (inferenceIndex == 0)
336 {
337 // Only add kernel measurement once, in case of multiple inferences
338 JsonChildObject measurementObject{ instrumentMeasurements[measurementIndex].m_Name };
339 measurementObject.SetUnit(instrumentMeasurements[measurementIndex].m_Unit);
340 measurementObject.SetType(JsonObjectType::Measurement);
341
342 ARMNN_ASSERT(parentObject.NumChildren() == childIdx);
343 parentObject.AddChild(measurementObject);
344 }
345 else
346 {
347 if (numSkippedKernels > 0)
348 {
349 parentObject.GetChild(--numSkippedKernels).AddMeasurement(0.0);
350 }
351 }
352
353 parentObject.GetChild(childIdx).AddMeasurement(instrumentMeasurements[measurementIndex].m_Value);
354 }
355
356 auto childEventsIt = descendantsMap.find(parentEvent);
357 if (childEventsIt != descendantsMap.end())
358 {
359 for (auto childEvent : childEventsIt->second)
360 {
361 if (inferenceIndex == 0)
362 {
363 // Only add second level once, in case of multiple inferences
364 JsonChildObject childObject{ childEvent->GetName() };
365 childObject.SetType(JsonObjectType::Event);
366 parentObject.AddChild(childObject);
367 }
368
369 // It's possible that childIdx can overrun the parents' child vector. Check before we try to process a
370 // non-existent child.
371 if (childIdx < parentObject.NumChildren())
372 {
373 // Recursively process children.
374 ExtractJsonObjects(inferenceIndex, childEvent, parentObject.GetChild(childIdx), descendantsMap);
375 childIdx++;
376 }
377 }
378 }
379 }
380
Print(std::ostream & outStream) const381 void ProfilerImpl::Print(std::ostream& outStream) const
382 {
383 // Makes sure timestamps are output with 6 decimals, and save old settings.
384 std::streamsize oldPrecision = outStream.precision();
385 outStream.precision(6);
386 std::ios_base::fmtflags oldFlags = outStream.flags();
387 outStream.setf(std::ios::fixed);
388 JsonPrinter printer(outStream);
389
390 // First find all the parent Events and print out duration measurements.
391 int baseLevel = -1;
392
393 std::vector<const Event*> optimizations;
394 PopulateParent(optimizations, baseLevel, "Optimizer");
395
396 std::vector<const Event*> loadedNetworks;
397 PopulateParent(loadedNetworks, baseLevel, "LoadedNetwork");
398
399 std::vector<const Event*> inferences;
400 PopulateParent(inferences, baseLevel, "EnqueueWorkload");
401
402 // Second map out descendants hierarchy
403 std::map<const Event*, std::vector<const Event*>> descendantsMap;
404 PopulateDescendants(descendantsMap);
405
406 // Extract json objects for each parent event type
407 JsonChildObject optimizeObject{ "optimize_measurements" };
408
409 for (unsigned int optimizeIndex = 0; optimizeIndex < optimizations.size(); ++optimizeIndex)
410 {
411 auto optimization = optimizations[optimizeIndex];
412 ExtractJsonObjects(optimizeIndex, optimization, optimizeObject, descendantsMap);
413 }
414
415 JsonChildObject loadedNetworkObject{ "loaded_network_measurements" };
416
417 for (unsigned int loadedNetworkIndex = 0; loadedNetworkIndex < loadedNetworks.size(); ++loadedNetworkIndex)
418 {
419 auto loadedNetwork = loadedNetworks[loadedNetworkIndex];
420 ExtractJsonObjects(loadedNetworkIndex, loadedNetwork, loadedNetworkObject, descendantsMap);
421 }
422
423 JsonChildObject inferenceObject{ "inference_measurements" };
424
425 for (unsigned int inferenceIndex = 0; inferenceIndex < inferences.size(); ++inferenceIndex)
426 {
427 auto inference = inferences[inferenceIndex];
428 ExtractJsonObjects(inferenceIndex, inference, inferenceObject, descendantsMap);
429 }
430
431 printer.PrintHeader();
432 printer.PrintArmNNHeader();
433
434 if (m_ProfilingDetails.get()->DetailsExist() &&
435 (m_DetailsToStdOutMethod == ProfilingDetailsMethod::DetailsOnly
436 || m_DetailsToStdOutMethod == ProfilingDetailsMethod::DetailsWithEvents))
437 {
438 JsonChildObject detailsObject{ "layer_details" };
439 if (m_DetailsToStdOutMethod == ProfilingDetailsMethod::DetailsOnly)
440 {
441 detailsObject.EnableDetailsOnly();
442 }
443 detailsObject.SetType(JsonObjectType::ExecObjectDesc);
444 detailsObject.SetAndParseDetails(m_ProfilingDetails.get()->GetProfilingDetails());
445
446 size_t id = 0;
447 printer.PrintJsonChildObject(detailsObject, id);
448 }
449
450 // print inference object, also prints child layer and kernel measurements
451 size_t id = 0;
452 if (m_DetailsToStdOutMethod != ProfilingDetailsMethod::DetailsOnly)
453 {
454 printer.PrintJsonChildObject(optimizeObject, id);
455 printer.PrintSeparator();
456 printer.PrintNewLine();
457 printer.PrintJsonChildObject(loadedNetworkObject, id);
458 printer.PrintSeparator();
459 printer.PrintNewLine();
460 printer.PrintJsonChildObject(inferenceObject, id);
461 printer.PrintNewLine();
462 }
463 // end of ArmNN
464 printer.PrintFooter();
465
466 // end of main JSON object
467 printer.PrintNewLine();
468 printer.PrintFooter();
469 printer.PrintNewLine();
470
471 // Restores previous precision settings.
472 outStream.flags(oldFlags);
473 outStream.precision(oldPrecision);
474
475 if (m_DetailsToStdOutMethod == ProfilingDetailsMethod::DetailsOnly)
476 {
477 exit(0);
478 }
479 }
480
AnalyzeEventsAndWriteResults(std::ostream & outStream) const481 void ProfilerImpl::AnalyzeEventsAndWriteResults(std::ostream& outStream) const
482 {
483 // Stack should be empty now.
484 const bool saneMarkerSequence = m_Parents.empty();
485
486 // Abort if the sequence of markers was found to have incorrect information:
487 // The stats cannot be trusted.
488 if (!saneMarkerSequence)
489 {
490 outStream << "Cannot write profiling stats. "
491 "Unexpected errors were found when analyzing the sequence of logged events, "
492 "which may lead to plainly wrong stats. The profiling system may contain implementation "
493 "issues or could have been used in an unsafe manner." << std::endl;
494 return;
495 }
496
497 // Analyzes the full sequence of events.
498 AnalyzeEventSequenceAndWriteResults(m_EventSequence.cbegin(),
499 m_EventSequence.cend(),
500 outStream);
501
502 // Aggregates events by tag if requested (spams the output stream if done for all tags).
503 if (g_AggregateProfilingEventsByInference)
504 {
505 outStream << std::endl;
506 outStream << "***" << std::endl;
507 outStream << "*** Per Inference Stats" << std::endl;
508 outStream << "***" << std::endl;
509 outStream << std::endl;
510
511 int baseLevel = -1;
512 std::vector<const Event*> inferences;
513 PopulateParent(inferences, baseLevel, "EnqueueWorkload");
514
515 // Second map out descendants hierarchy
516 std::map<const Event*, std::vector<const Event*>> descendantsMap;
517 PopulateDescendants(descendantsMap);
518
519 std::function<void(const Event*, std::vector<const Event*>&)>
520 FindDescendantEvents = [&](const Event* eventPtr, std::vector<const Event*>& sequence)
521 {
522 sequence.push_back(eventPtr);
523
524 if (CalcLevel(eventPtr) > baseLevel+2) //We only care about levels as deep as workload executions.
525 {
526 return;
527 }
528
529 auto children = descendantsMap.find(eventPtr);
530 if (children == descendantsMap.end())
531 {
532 return;
533 }
534
535 if (!(children->second.empty()))
536 {
537 return FindDescendantEvents(children->second[0], sequence);
538 }
539 };
540
541 // Third, find events belonging to each inference
542 int inferenceIdx = 0;
543 for (auto inference : inferences)
544 {
545 std::vector<const Event*> sequence;
546
547 //build sequence, depth first
548 FindDescendantEvents(inference, sequence);
549
550 outStream << "> Begin Inference: " << inferenceIdx << std::endl;
551 outStream << std::endl;
552 AnalyzeEventSequenceAndWriteResults(sequence.cbegin(),
553 sequence.cend(),
554 outStream);
555 outStream << std::endl;
556 outStream << "> End Inference: " << inferenceIdx << std::endl;
557
558 inferenceIdx++;
559 }
560 }
561 }
562
GetEventColor(const BackendId & backendId) const563 std::uint32_t ProfilerImpl::GetEventColor(const BackendId& backendId) const
564 {
565 static BackendId cpuRef("CpuRef");
566 static BackendId cpuAcc("CpuAcc");
567 static BackendId gpuAcc("GpuAcc");
568 if (backendId == cpuRef)
569 {
570 // Cyan
571 return 0xffff001b;
572 }
573 else if (backendId == cpuAcc)
574 {
575 // Green
576 return 0x00ff001b;
577 }
578 else if (backendId == gpuAcc)
579 {
580 // Purple
581 return 0xff007f1b;
582 }
583 else
584 {
585 // Dark gray
586 return 0x5555551b;
587 }
588 }
589
590 // The thread_local pointer to the profiler instance.
591 thread_local IProfiler* tl_Profiler = nullptr;
592
GetInstance()593 ProfilerManager& ProfilerManager::GetInstance()
594 {
595 // Global reference to the single ProfileManager instance allowed.
596 static ProfilerManager s_ProfilerManager;
597 return s_ProfilerManager;
598 }
599
RegisterProfiler(IProfiler * profiler)600 void ProfilerManager::RegisterProfiler(IProfiler* profiler)
601 {
602 tl_Profiler = profiler;
603 }
604
GetProfiler()605 IProfiler* ProfilerManager::GetProfiler()
606 {
607 return tl_Profiler;
608 }
609
EnableProfiling(bool enableProfiling)610 void IProfiler::EnableProfiling(bool enableProfiling)
611 {
612 pProfilerImpl->EnableProfiling(enableProfiling);
613 }
614
EnableNetworkDetailsToStdOut(ProfilingDetailsMethod detailsMethod)615 void IProfiler::EnableNetworkDetailsToStdOut(ProfilingDetailsMethod detailsMethod)
616 {
617 pProfilerImpl->EnableNetworkDetailsToStdOut(detailsMethod);
618 }
619
IsProfilingEnabled()620 bool IProfiler::IsProfilingEnabled()
621 {
622 return pProfilerImpl->IsProfilingEnabled();
623 }
624
AnalyzeEventsAndWriteResults(std::ostream & outStream) const625 void IProfiler::AnalyzeEventsAndWriteResults(std::ostream& outStream) const
626 {
627 pProfilerImpl->AnalyzeEventsAndWriteResults(outStream);
628 }
629
Print(std::ostream & outStream) const630 void IProfiler::Print(std::ostream& outStream) const
631 {
632 pProfilerImpl->Print(outStream);
633 }
634
BeginEvent(const BackendId & backendId,const std::string & label,std::vector<InstrumentPtr> && instruments,const Optional<arm::pipe::ProfilingGuid> & guid)635 Event* IProfiler::BeginEvent(const BackendId& backendId,
636 const std::string& label,
637 std::vector<InstrumentPtr>&& instruments,
638 const Optional<arm::pipe::ProfilingGuid>& guid)
639 {
640 return pProfilerImpl->BeginEvent(this, backendId, label, std::move(instruments), guid);
641 }
642
643 IProfiler::~IProfiler() = default;
IProfiler()644 IProfiler::IProfiler() : pProfilerImpl(new ProfilerImpl())
645 {};
646
647 } // namespace armnn
648