| //===--------------------- BottleneckAnalysis.cpp ---------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// \file | |
| /// | |
| /// This file implements the functionalities used by the BottleneckAnalysis | |
| /// to report bottleneck info. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "Views/BottleneckAnalysis.h" | |
| #include "llvm/MC/MCInst.h" | |
| #include "llvm/MCA/Support.h" | |
| #include "llvm/Support/Format.h" | |
| namespace llvm { | |
| namespace mca { | |
| #define DEBUG_TYPE "llvm-mca" | |
| PressureTracker::PressureTracker(const MCSchedModel &Model) | |
| : SM(Model), | |
| ResourcePressureDistribution(Model.getNumProcResourceKinds(), 0), | |
| ProcResID2Mask(Model.getNumProcResourceKinds(), 0), | |
| ResIdx2ProcResID(Model.getNumProcResourceKinds(), 0), | |
| ProcResID2ResourceUsersIndex(Model.getNumProcResourceKinds(), 0) { | |
| computeProcResourceMasks(SM, ProcResID2Mask); | |
| // Ignore the invalid resource at index zero. | |
| unsigned NextResourceUsersIdx = 0; | |
| for (unsigned I = 1, E = Model.getNumProcResourceKinds(); I < E; ++I) { | |
| const MCProcResourceDesc &ProcResource = *SM.getProcResource(I); | |
| ProcResID2ResourceUsersIndex[I] = NextResourceUsersIdx; | |
| NextResourceUsersIdx += ProcResource.NumUnits; | |
| uint64_t ResourceMask = ProcResID2Mask[I]; | |
| ResIdx2ProcResID[getResourceStateIndex(ResourceMask)] = I; | |
| } | |
| ResourceUsers.resize(NextResourceUsersIdx); | |
| llvm::fill(ResourceUsers, std::make_pair<unsigned, unsigned>(~0U, 0U)); | |
| } | |
| void PressureTracker::getResourceUsers(uint64_t ResourceMask, | |
| SmallVectorImpl<User> &Users) const { | |
| unsigned Index = getResourceStateIndex(ResourceMask); | |
| unsigned ProcResID = ResIdx2ProcResID[Index]; | |
| const MCProcResourceDesc &PRDesc = *SM.getProcResource(ProcResID); | |
| for (unsigned I = 0, E = PRDesc.NumUnits; I < E; ++I) { | |
| const User U = getResourceUser(ProcResID, I); | |
| if (U.second && IPI.contains(U.first)) | |
| Users.emplace_back(U); | |
| } | |
| } | |
| void PressureTracker::onInstructionDispatched(unsigned IID) { | |
| IPI.try_emplace(IID); | |
| } | |
| void PressureTracker::onInstructionExecuted(unsigned IID) { IPI.erase(IID); } | |
| void PressureTracker::handleInstructionIssuedEvent( | |
| const HWInstructionIssuedEvent &Event) { | |
| unsigned IID = Event.IR.getSourceIndex(); | |
| for (const ResourceUse &Use : Event.UsedResources) { | |
| const ResourceRef &RR = Use.first; | |
| unsigned Index = ProcResID2ResourceUsersIndex[RR.first]; | |
| Index += llvm::countr_zero(RR.second); | |
| ResourceUsers[Index] = std::make_pair(IID, Use.second.getNumerator()); | |
| } | |
| } | |
| void PressureTracker::updateResourcePressureDistribution( | |
| uint64_t CumulativeMask) { | |
| while (CumulativeMask) { | |
| uint64_t Current = CumulativeMask & (-CumulativeMask); | |
| unsigned ResIdx = getResourceStateIndex(Current); | |
| unsigned ProcResID = ResIdx2ProcResID[ResIdx]; | |
| uint64_t Mask = ProcResID2Mask[ProcResID]; | |
| if (Mask == Current) { | |
| ResourcePressureDistribution[ProcResID]++; | |
| CumulativeMask ^= Current; | |
| continue; | |
| } | |
| Mask ^= Current; | |
| while (Mask) { | |
| uint64_t SubUnit = Mask & (-Mask); | |
| ResIdx = getResourceStateIndex(SubUnit); | |
| ProcResID = ResIdx2ProcResID[ResIdx]; | |
| ResourcePressureDistribution[ProcResID]++; | |
| Mask ^= SubUnit; | |
| } | |
| CumulativeMask ^= Current; | |
| } | |
| } | |
| void PressureTracker::handlePressureEvent(const HWPressureEvent &Event) { | |
| assert(Event.Reason != HWPressureEvent::INVALID && | |
| "Unexpected invalid event!"); | |
| switch (Event.Reason) { | |
| default: | |
| break; | |
| case HWPressureEvent::RESOURCES: { | |
| const uint64_t ResourceMask = Event.ResourceMask; | |
| updateResourcePressureDistribution(Event.ResourceMask); | |
| for (const InstRef &IR : Event.AffectedInstructions) { | |
| const Instruction &IS = *IR.getInstruction(); | |
| unsigned BusyResources = IS.getCriticalResourceMask() & ResourceMask; | |
| if (!BusyResources) | |
| continue; | |
| unsigned IID = IR.getSourceIndex(); | |
| IPI[IID].ResourcePressureCycles++; | |
| } | |
| break; | |
| } | |
| case HWPressureEvent::REGISTER_DEPS: | |
| for (const InstRef &IR : Event.AffectedInstructions) { | |
| unsigned IID = IR.getSourceIndex(); | |
| IPI[IID].RegisterPressureCycles++; | |
| } | |
| break; | |
| case HWPressureEvent::MEMORY_DEPS: | |
| for (const InstRef &IR : Event.AffectedInstructions) { | |
| unsigned IID = IR.getSourceIndex(); | |
| IPI[IID].MemoryPressureCycles++; | |
| } | |
| } | |
| } | |
| #ifndef NDEBUG | |
| void DependencyGraph::dumpDependencyEdge(raw_ostream &OS, | |
| const DependencyEdge &DepEdge, | |
| MCInstPrinter &MCIP) const { | |
| unsigned FromIID = DepEdge.FromIID; | |
| unsigned ToIID = DepEdge.ToIID; | |
| assert(FromIID < ToIID && "Graph should be acyclic!"); | |
| const DependencyEdge::Dependency &DE = DepEdge.Dep; | |
| assert(DE.Type != DependencyEdge::DT_INVALID && "Unexpected invalid edge!"); | |
| OS << " FROM: " << FromIID << " TO: " << ToIID << " "; | |
| if (DE.Type == DependencyEdge::DT_REGISTER) { | |
| OS << " - REGISTER: "; | |
| MCIP.printRegName(OS, DE.ResourceOrRegID); | |
| } else if (DE.Type == DependencyEdge::DT_MEMORY) { | |
| OS << " - MEMORY"; | |
| } else { | |
| assert(DE.Type == DependencyEdge::DT_RESOURCE && | |
| "Unsupported dependency type!"); | |
| OS << " - RESOURCE MASK: " << DE.ResourceOrRegID; | |
| } | |
| OS << " - COST: " << DE.Cost << '\n'; | |
| } | |
| #endif // NDEBUG | |
| void DependencyGraph::pruneEdges(unsigned Iterations) { | |
| for (DGNode &N : Nodes) { | |
| unsigned NumPruned = 0; | |
| const unsigned Size = N.OutgoingEdges.size(); | |
| // Use a cut-off threshold to prune edges with a low frequency. | |
| for (unsigned I = 0, E = Size; I < E; ++I) { | |
| DependencyEdge &Edge = N.OutgoingEdges[I]; | |
| if (Edge.Frequency == Iterations) | |
| continue; | |
| double Factor = (double)Edge.Frequency / Iterations; | |
| if (0.10 < Factor) | |
| continue; | |
| Nodes[Edge.ToIID].NumPredecessors--; | |
| std::swap(Edge, N.OutgoingEdges[E - 1]); | |
| --E; | |
| ++NumPruned; | |
| } | |
| if (NumPruned) | |
| N.OutgoingEdges.resize(Size - NumPruned); | |
| } | |
| } | |
| void DependencyGraph::initializeRootSet( | |
| SmallVectorImpl<unsigned> &RootSet) const { | |
| for (unsigned I = 0, E = Nodes.size(); I < E; ++I) { | |
| const DGNode &N = Nodes[I]; | |
| if (N.NumPredecessors == 0 && !N.OutgoingEdges.empty()) | |
| RootSet.emplace_back(I); | |
| } | |
| } | |
| void DependencyGraph::propagateThroughEdges(SmallVectorImpl<unsigned> &RootSet, | |
| unsigned Iterations) { | |
| SmallVector<unsigned, 8> ToVisit; | |
| // A critical sequence is computed as the longest path from a node of the | |
| // RootSet to a leaf node (i.e. a node with no successors). The RootSet is | |
| // composed of nodes with at least one successor, and no predecessors. | |
| // | |
| // Each node of the graph starts with an initial default cost of zero. The | |
| // cost of a node is a measure of criticality: the higher the cost, the bigger | |
| // is the performance impact. | |
| // For register and memory dependencies, the cost is a function of the write | |
| // latency as well as the actual delay (in cycles) caused to users. | |
| // For processor resource dependencies, the cost is a function of the resource | |
| // pressure. Resource interferences with low frequency values are ignored. | |
| // | |
| // This algorithm is very similar to a (reverse) Dijkstra. Every iteration of | |
| // the inner loop selects (i.e. visits) a node N from a set of `unvisited | |
| // nodes`, and then propagates the cost of N to all its neighbors. | |
| // | |
| // The `unvisited nodes` set initially contains all the nodes from the | |
| // RootSet. A node N is added to the `unvisited nodes` if all its | |
| // predecessors have been visited already. | |
| // | |
| // For simplicity, every node tracks the number of unvisited incoming edges in | |
| // field `NumVisitedPredecessors`. When the value of that field drops to | |
| // zero, then the corresponding node is added to a `ToVisit` set. | |
| // | |
| // At the end of every iteration of the outer loop, set `ToVisit` becomes our | |
| // new `unvisited nodes` set. | |
| // | |
| // The algorithm terminates when the set of unvisited nodes (i.e. our RootSet) | |
| // is empty. This algorithm works under the assumption that the graph is | |
| // acyclic. | |
| do { | |
| for (unsigned IID : RootSet) { | |
| const DGNode &N = Nodes[IID]; | |
| for (const DependencyEdge &DepEdge : N.OutgoingEdges) { | |
| unsigned ToIID = DepEdge.ToIID; | |
| DGNode &To = Nodes[ToIID]; | |
| uint64_t Cost = N.Cost + DepEdge.Dep.Cost; | |
| // Check if this is the most expensive incoming edge seen so far. In | |
| // case, update the total cost of the destination node (ToIID), as well | |
| // its field `CriticalPredecessor`. | |
| if (Cost > To.Cost) { | |
| To.CriticalPredecessor = DepEdge; | |
| To.Cost = Cost; | |
| To.Depth = N.Depth + 1; | |
| } | |
| To.NumVisitedPredecessors++; | |
| if (To.NumVisitedPredecessors == To.NumPredecessors) | |
| ToVisit.emplace_back(ToIID); | |
| } | |
| } | |
| std::swap(RootSet, ToVisit); | |
| ToVisit.clear(); | |
| } while (!RootSet.empty()); | |
| } | |
| void DependencyGraph::getCriticalSequence( | |
| SmallVectorImpl<const DependencyEdge *> &Seq) const { | |
| // At this stage, nodes of the graph have been already visited, and costs have | |
| // been propagated through the edges (see method `propagateThroughEdges()`). | |
| // Identify the node N with the highest cost in the graph. By construction, | |
| // that node is the last instruction of our critical sequence. | |
| // Field N.Depth would tell us the total length of the sequence. | |
| // | |
| // To obtain the sequence of critical edges, we simply follow the chain of | |
| // critical predecessors starting from node N (field | |
| // DGNode::CriticalPredecessor). | |
| const auto It = | |
| llvm::max_element(Nodes, [](const DGNode &Lhs, const DGNode &Rhs) { | |
| return Lhs.Cost < Rhs.Cost; | |
| }); | |
| unsigned IID = std::distance(Nodes.begin(), It); | |
| Seq.resize(Nodes[IID].Depth); | |
| for (const DependencyEdge *&DE : llvm::reverse(Seq)) { | |
| const DGNode &N = Nodes[IID]; | |
| DE = &N.CriticalPredecessor; | |
| IID = N.CriticalPredecessor.FromIID; | |
| } | |
| } | |
| void BottleneckAnalysis::printInstruction(formatted_raw_ostream &FOS, | |
| const MCInst &MCI, | |
| bool UseDifferentColor) const { | |
| FOS.PadToColumn(14); | |
| if (UseDifferentColor) | |
| FOS.changeColor(raw_ostream::CYAN, true, false); | |
| FOS << printInstructionString(MCI); | |
| if (UseDifferentColor) | |
| FOS.resetColor(); | |
| } | |
| void BottleneckAnalysis::printCriticalSequence(raw_ostream &OS) const { | |
| // Early exit if no bottlenecks were found during the simulation. | |
| if (!SeenStallCycles || !BPI.PressureIncreaseCycles) | |
| return; | |
| SmallVector<const DependencyEdge *, 16> Seq; | |
| DG.getCriticalSequence(Seq); | |
| if (Seq.empty()) | |
| return; | |
| OS << "\nCritical sequence based on the simulation:\n\n"; | |
| const DependencyEdge &FirstEdge = *Seq[0]; | |
| ArrayRef<llvm::MCInst> Source = getSource(); | |
| unsigned FromIID = FirstEdge.FromIID % Source.size(); | |
| unsigned ToIID = FirstEdge.ToIID % Source.size(); | |
| bool IsLoopCarried = FromIID >= ToIID; | |
| formatted_raw_ostream FOS(OS); | |
| FOS.PadToColumn(14); | |
| FOS << "Instruction"; | |
| FOS.PadToColumn(58); | |
| FOS << "Dependency Information"; | |
| bool HasColors = FOS.has_colors(); | |
| unsigned CurrentIID = 0; | |
| if (IsLoopCarried) { | |
| FOS << "\n +----< " << FromIID << "."; | |
| printInstruction(FOS, Source[FromIID], HasColors); | |
| FOS << "\n |\n | < loop carried > \n |"; | |
| } else { | |
| while (CurrentIID < FromIID) { | |
| FOS << "\n " << CurrentIID << "."; | |
| printInstruction(FOS, Source[CurrentIID]); | |
| CurrentIID++; | |
| } | |
| FOS << "\n +----< " << CurrentIID << "."; | |
| printInstruction(FOS, Source[CurrentIID], HasColors); | |
| CurrentIID++; | |
| } | |
| for (const DependencyEdge *&DE : Seq) { | |
| ToIID = DE->ToIID % Source.size(); | |
| unsigned LastIID = CurrentIID > ToIID ? Source.size() : ToIID; | |
| while (CurrentIID < LastIID) { | |
| FOS << "\n | " << CurrentIID << "."; | |
| printInstruction(FOS, Source[CurrentIID]); | |
| CurrentIID++; | |
| } | |
| if (CurrentIID == ToIID) { | |
| FOS << "\n +----> " << ToIID << "."; | |
| printInstruction(FOS, Source[CurrentIID], HasColors); | |
| } else { | |
| FOS << "\n |\n | < loop carried > \n |" | |
| << "\n +----> " << ToIID << "."; | |
| printInstruction(FOS, Source[ToIID], HasColors); | |
| } | |
| FOS.PadToColumn(58); | |
| const DependencyEdge::Dependency &Dep = DE->Dep; | |
| if (HasColors) | |
| FOS.changeColor(raw_ostream::SAVEDCOLOR, true, false); | |
| if (Dep.Type == DependencyEdge::DT_REGISTER) { | |
| FOS << "## REGISTER dependency: "; | |
| if (HasColors) | |
| FOS.changeColor(raw_ostream::MAGENTA, true, false); | |
| getInstPrinter().printRegName(FOS, Dep.ResourceOrRegID); | |
| } else if (Dep.Type == DependencyEdge::DT_MEMORY) { | |
| FOS << "## MEMORY dependency."; | |
| } else { | |
| assert(Dep.Type == DependencyEdge::DT_RESOURCE && | |
| "Unsupported dependency type!"); | |
| FOS << "## RESOURCE interference: "; | |
| if (HasColors) | |
| FOS.changeColor(raw_ostream::MAGENTA, true, false); | |
| FOS << Tracker.resolveResourceName(Dep.ResourceOrRegID); | |
| if (HasColors) { | |
| FOS.resetColor(); | |
| FOS.changeColor(raw_ostream::SAVEDCOLOR, true, false); | |
| } | |
| FOS << " [ probability: " << ((DE->Frequency * 100) / Iterations) | |
| << "% ]"; | |
| } | |
| if (HasColors) | |
| FOS.resetColor(); | |
| ++CurrentIID; | |
| } | |
| while (CurrentIID < Source.size()) { | |
| FOS << "\n " << CurrentIID << "."; | |
| printInstruction(FOS, Source[CurrentIID]); | |
| CurrentIID++; | |
| } | |
| FOS << '\n'; | |
| FOS.flush(); | |
| } | |
| #ifndef NDEBUG | |
| void DependencyGraph::dump(raw_ostream &OS, MCInstPrinter &MCIP) const { | |
| OS << "\nREG DEPS\n"; | |
| for (const DGNode &Node : Nodes) | |
| for (const DependencyEdge &DE : Node.OutgoingEdges) | |
| if (DE.Dep.Type == DependencyEdge::DT_REGISTER) | |
| dumpDependencyEdge(OS, DE, MCIP); | |
| OS << "\nMEM DEPS\n"; | |
| for (const DGNode &Node : Nodes) | |
| for (const DependencyEdge &DE : Node.OutgoingEdges) | |
| if (DE.Dep.Type == DependencyEdge::DT_MEMORY) | |
| dumpDependencyEdge(OS, DE, MCIP); | |
| OS << "\nRESOURCE DEPS\n"; | |
| for (const DGNode &Node : Nodes) | |
| for (const DependencyEdge &DE : Node.OutgoingEdges) | |
| if (DE.Dep.Type == DependencyEdge::DT_RESOURCE) | |
| dumpDependencyEdge(OS, DE, MCIP); | |
| } | |
| #endif // NDEBUG | |
| void DependencyGraph::addDependency(unsigned From, unsigned To, | |
| DependencyEdge::Dependency &&Dep) { | |
| DGNode &NodeFrom = Nodes[From]; | |
| DGNode &NodeTo = Nodes[To]; | |
| SmallVectorImpl<DependencyEdge> &Vec = NodeFrom.OutgoingEdges; | |
| auto It = find_if(Vec, [To, Dep](DependencyEdge &DE) { | |
| return DE.ToIID == To && DE.Dep.ResourceOrRegID == Dep.ResourceOrRegID; | |
| }); | |
| if (It != Vec.end()) { | |
| It->Dep.Cost += Dep.Cost; | |
| It->Frequency++; | |
| return; | |
| } | |
| DependencyEdge DE = {Dep, From, To, 1}; | |
| Vec.emplace_back(DE); | |
| NodeTo.NumPredecessors++; | |
| } | |
| BottleneckAnalysis::BottleneckAnalysis(const MCSubtargetInfo &sti, | |
| MCInstPrinter &Printer, | |
| ArrayRef<MCInst> S, unsigned NumIter) | |
| : InstructionView(sti, Printer, S), Tracker(sti.getSchedModel()), | |
| DG(S.size() * 3), Iterations(NumIter), TotalCycles(0), | |
| PressureIncreasedBecauseOfResources(false), | |
| PressureIncreasedBecauseOfRegisterDependencies(false), | |
| PressureIncreasedBecauseOfMemoryDependencies(false), | |
| SeenStallCycles(false), BPI() {} | |
| void BottleneckAnalysis::addRegisterDep(unsigned From, unsigned To, | |
| unsigned RegID, unsigned Cost) { | |
| bool IsLoopCarried = From >= To; | |
| unsigned SourceSize = getSource().size(); | |
| if (IsLoopCarried) { | |
| DG.addRegisterDep(From, To + SourceSize, RegID, Cost); | |
| DG.addRegisterDep(From + SourceSize, To + (SourceSize * 2), RegID, Cost); | |
| return; | |
| } | |
| DG.addRegisterDep(From + SourceSize, To + SourceSize, RegID, Cost); | |
| } | |
| void BottleneckAnalysis::addMemoryDep(unsigned From, unsigned To, | |
| unsigned Cost) { | |
| bool IsLoopCarried = From >= To; | |
| unsigned SourceSize = getSource().size(); | |
| if (IsLoopCarried) { | |
| DG.addMemoryDep(From, To + SourceSize, Cost); | |
| DG.addMemoryDep(From + SourceSize, To + (SourceSize * 2), Cost); | |
| return; | |
| } | |
| DG.addMemoryDep(From + SourceSize, To + SourceSize, Cost); | |
| } | |
| void BottleneckAnalysis::addResourceDep(unsigned From, unsigned To, | |
| uint64_t Mask, unsigned Cost) { | |
| bool IsLoopCarried = From >= To; | |
| unsigned SourceSize = getSource().size(); | |
| if (IsLoopCarried) { | |
| DG.addResourceDep(From, To + SourceSize, Mask, Cost); | |
| DG.addResourceDep(From + SourceSize, To + (SourceSize * 2), Mask, Cost); | |
| return; | |
| } | |
| DG.addResourceDep(From + SourceSize, To + SourceSize, Mask, Cost); | |
| } | |
| void BottleneckAnalysis::onEvent(const HWInstructionEvent &Event) { | |
| const unsigned IID = Event.IR.getSourceIndex(); | |
| if (Event.Type == HWInstructionEvent::Dispatched) { | |
| Tracker.onInstructionDispatched(IID); | |
| return; | |
| } | |
| if (Event.Type == HWInstructionEvent::Executed) { | |
| Tracker.onInstructionExecuted(IID); | |
| return; | |
| } | |
| if (Event.Type != HWInstructionEvent::Issued) | |
| return; | |
| ArrayRef<llvm::MCInst> Source = getSource(); | |
| const Instruction &IS = *Event.IR.getInstruction(); | |
| unsigned To = IID % Source.size(); | |
| unsigned Cycles = 2 * Tracker.getResourcePressureCycles(IID); | |
| uint64_t ResourceMask = IS.getCriticalResourceMask(); | |
| SmallVector<std::pair<unsigned, unsigned>, 4> Users; | |
| while (ResourceMask) { | |
| uint64_t Current = ResourceMask & (-ResourceMask); | |
| Tracker.getResourceUsers(Current, Users); | |
| for (const std::pair<unsigned, unsigned> &U : Users) | |
| addResourceDep(U.first % Source.size(), To, Current, U.second + Cycles); | |
| Users.clear(); | |
| ResourceMask ^= Current; | |
| } | |
| const CriticalDependency &RegDep = IS.getCriticalRegDep(); | |
| if (RegDep.Cycles) { | |
| Cycles = RegDep.Cycles + 2 * Tracker.getRegisterPressureCycles(IID); | |
| unsigned From = RegDep.IID % Source.size(); | |
| addRegisterDep(From, To, RegDep.RegID, Cycles); | |
| } | |
| const CriticalDependency &MemDep = IS.getCriticalMemDep(); | |
| if (MemDep.Cycles) { | |
| Cycles = MemDep.Cycles + 2 * Tracker.getMemoryPressureCycles(IID); | |
| unsigned From = MemDep.IID % Source.size(); | |
| addMemoryDep(From, To, Cycles); | |
| } | |
| Tracker.handleInstructionIssuedEvent( | |
| static_cast<const HWInstructionIssuedEvent &>(Event)); | |
| // Check if this is the last simulated instruction. | |
| if (IID == ((Iterations * Source.size()) - 1)) | |
| DG.finalizeGraph(Iterations); | |
| } | |
| void BottleneckAnalysis::onEvent(const HWPressureEvent &Event) { | |
| assert(Event.Reason != HWPressureEvent::INVALID && | |
| "Unexpected invalid event!"); | |
| Tracker.handlePressureEvent(Event); | |
| switch (Event.Reason) { | |
| default: | |
| break; | |
| case HWPressureEvent::RESOURCES: | |
| PressureIncreasedBecauseOfResources = true; | |
| break; | |
| case HWPressureEvent::REGISTER_DEPS: | |
| PressureIncreasedBecauseOfRegisterDependencies = true; | |
| break; | |
| case HWPressureEvent::MEMORY_DEPS: | |
| PressureIncreasedBecauseOfMemoryDependencies = true; | |
| break; | |
| } | |
| } | |
| void BottleneckAnalysis::onCycleEnd() { | |
| ++TotalCycles; | |
| bool PressureIncreasedBecauseOfDataDependencies = | |
| PressureIncreasedBecauseOfRegisterDependencies || | |
| PressureIncreasedBecauseOfMemoryDependencies; | |
| if (!PressureIncreasedBecauseOfResources && | |
| !PressureIncreasedBecauseOfDataDependencies) | |
| return; | |
| ++BPI.PressureIncreaseCycles; | |
| if (PressureIncreasedBecauseOfRegisterDependencies) | |
| ++BPI.RegisterDependencyCycles; | |
| if (PressureIncreasedBecauseOfMemoryDependencies) | |
| ++BPI.MemoryDependencyCycles; | |
| if (PressureIncreasedBecauseOfDataDependencies) | |
| ++BPI.DataDependencyCycles; | |
| if (PressureIncreasedBecauseOfResources) | |
| ++BPI.ResourcePressureCycles; | |
| PressureIncreasedBecauseOfResources = false; | |
| PressureIncreasedBecauseOfRegisterDependencies = false; | |
| PressureIncreasedBecauseOfMemoryDependencies = false; | |
| } | |
| void BottleneckAnalysis::printBottleneckHints(raw_ostream &OS) const { | |
| if (!SeenStallCycles || !BPI.PressureIncreaseCycles) { | |
| OS << "\n\nNo resource or data dependency bottlenecks discovered.\n"; | |
| return; | |
| } | |
| double PressurePerCycle = | |
| (double)BPI.PressureIncreaseCycles * 100 / TotalCycles; | |
| double ResourcePressurePerCycle = | |
| (double)BPI.ResourcePressureCycles * 100 / TotalCycles; | |
| double DDPerCycle = (double)BPI.DataDependencyCycles * 100 / TotalCycles; | |
| double RegDepPressurePerCycle = | |
| (double)BPI.RegisterDependencyCycles * 100 / TotalCycles; | |
| double MemDepPressurePerCycle = | |
| (double)BPI.MemoryDependencyCycles * 100 / TotalCycles; | |
| OS << "\n\nCycles with backend pressure increase [ " | |
| << format("%.2f", floor((PressurePerCycle * 100) + 0.5) / 100) << "% ]"; | |
| OS << "\nThroughput Bottlenecks: " | |
| << "\n Resource Pressure [ " | |
| << format("%.2f", floor((ResourcePressurePerCycle * 100) + 0.5) / 100) | |
| << "% ]"; | |
| if (BPI.PressureIncreaseCycles) { | |
| ArrayRef<unsigned> Distribution = Tracker.getResourcePressureDistribution(); | |
| const MCSchedModel &SM = getSubTargetInfo().getSchedModel(); | |
| for (unsigned I = 0, E = Distribution.size(); I < E; ++I) { | |
| unsigned ReleaseAtCycles = Distribution[I]; | |
| if (ReleaseAtCycles) { | |
| double Frequency = (double)ReleaseAtCycles * 100 / TotalCycles; | |
| const MCProcResourceDesc &PRDesc = *SM.getProcResource(I); | |
| OS << "\n - " << PRDesc.Name << " [ " | |
| << format("%.2f", floor((Frequency * 100) + 0.5) / 100) << "% ]"; | |
| } | |
| } | |
| } | |
| OS << "\n Data Dependencies: [ " | |
| << format("%.2f", floor((DDPerCycle * 100) + 0.5) / 100) << "% ]"; | |
| OS << "\n - Register Dependencies [ " | |
| << format("%.2f", floor((RegDepPressurePerCycle * 100) + 0.5) / 100) | |
| << "% ]"; | |
| OS << "\n - Memory Dependencies [ " | |
| << format("%.2f", floor((MemDepPressurePerCycle * 100) + 0.5) / 100) | |
| << "% ]\n"; | |
| } | |
| void BottleneckAnalysis::printView(raw_ostream &OS) const { | |
| std::string Buffer; | |
| raw_string_ostream TempStream(Buffer); | |
| printBottleneckHints(TempStream); | |
| OS << Buffer; | |
| printCriticalSequence(OS); | |
| } | |
| json::Value BottleneckAnalysis::toJSON() const { | |
| if (!SeenStallCycles || !BPI.PressureIncreaseCycles) { | |
| json::Object JO({{"PressureIncreaseCycles", 0}}); | |
| return JO; | |
| } | |
| json::Array CriticalSequence; | |
| // get critical sequence | |
| SmallVector<const DependencyEdge *, 16> Seq; | |
| DG.getCriticalSequence(Seq); | |
| if (!Seq.empty()) { | |
| for (const DependencyEdge *&DE : Seq) { | |
| json::Object DEJO({{"FromID", DE->FromIID}, | |
| {"ToID", DE->ToIID}, | |
| {"Type", static_cast<unsigned>(DE->Dep.Type)}, | |
| {"ResourceOrRegID", DE->Dep.ResourceOrRegID}}); | |
| CriticalSequence.push_back(std::move(DEJO)); | |
| } | |
| } | |
| json::Array ResourcePressure; | |
| if (BPI.PressureIncreaseCycles) { | |
| ArrayRef<unsigned> Distribution = Tracker.getResourcePressureDistribution(); | |
| const MCSchedModel &SM = getSubTargetInfo().getSchedModel(); | |
| for (unsigned I = 0, E = Distribution.size(); I < E; ++I) { | |
| unsigned ReleaseAtCycles = Distribution[I]; | |
| if (ReleaseAtCycles) { | |
| const MCProcResourceDesc &PRDesc = *SM.getProcResource(I); | |
| json::Object RPJO({{PRDesc.Name, ReleaseAtCycles}}); | |
| ResourcePressure.push_back(std::move(RPJO)); | |
| } | |
| } | |
| } | |
| json::Object JO({{"PressureIncreaseCycles", BPI.PressureIncreaseCycles}, | |
| {"ResourcePressureCycles", BPI.ResourcePressureCycles}, | |
| {"DataDependencyCycles", BPI.DataDependencyCycles}, | |
| {"RegisterDependencyCycles", BPI.RegisterDependencyCycles}, | |
| {"MemoryDependencyCycles", BPI.MemoryDependencyCycles}, | |
| {"TotalCycles", TotalCycles}, | |
| {"DependencyEdge", std::move(CriticalSequence)}, | |
| {"ResourcePressure", std::move(ResourcePressure)}}); | |
| return JO; | |
| } | |
| } // namespace mca. | |
| } // namespace llvm |