xref: /openbsd-src/gnu/llvm/llvm/tools/llvm-exegesis/lib/Clustering.cpp (revision d415bd752c734aee168c4ee86ff32e8cc249eb16)
109467b48Spatrick //===-- Clustering.cpp ------------------------------------------*- C++ -*-===//
209467b48Spatrick //
309467b48Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
409467b48Spatrick // See https://llvm.org/LICENSE.txt for license information.
509467b48Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
609467b48Spatrick //
709467b48Spatrick //===----------------------------------------------------------------------===//
809467b48Spatrick 
909467b48Spatrick #include "Clustering.h"
10097a140dSpatrick #include "Error.h"
11*d415bd75Srobert #include "SchedClassResolution.h"
12*d415bd75Srobert #include "llvm/ADT/MapVector.h"
1309467b48Spatrick #include "llvm/ADT/SetVector.h"
1409467b48Spatrick #include "llvm/ADT/SmallSet.h"
1509467b48Spatrick #include "llvm/ADT/SmallVector.h"
1609467b48Spatrick #include <algorithm>
17*d415bd75Srobert #include <deque>
1809467b48Spatrick #include <string>
1909467b48Spatrick #include <vector>
2009467b48Spatrick 
2109467b48Spatrick namespace llvm {
2209467b48Spatrick namespace exegesis {
2309467b48Spatrick 
2409467b48Spatrick // The clustering problem has the following characteristics:
2509467b48Spatrick //  (A) - Low dimension (dimensions are typically proc resource units,
2609467b48Spatrick //    typically < 10).
2709467b48Spatrick //  (B) - Number of points : ~thousands (points are measurements of an MCInst)
2809467b48Spatrick //  (C) - Number of clusters: ~tens.
2909467b48Spatrick //  (D) - The number of clusters is not known /a priory/.
3009467b48Spatrick //  (E) - The amount of noise is relatively small.
3109467b48Spatrick // The problem is rather small. In terms of algorithms, (D) disqualifies
3209467b48Spatrick // k-means and makes algorithms such as DBSCAN[1] or OPTICS[2] more applicable.
3309467b48Spatrick //
3409467b48Spatrick // We've used DBSCAN here because it's simple to implement. This is a pretty
3509467b48Spatrick // straightforward and inefficient implementation of the pseudocode in [2].
3609467b48Spatrick //
3709467b48Spatrick // [1] https://en.wikipedia.org/wiki/DBSCAN
3809467b48Spatrick // [2] https://en.wikipedia.org/wiki/OPTICS_algorithm
3909467b48Spatrick 
4009467b48Spatrick // Finds the points at distance less than sqrt(EpsilonSquared) of Q (not
4109467b48Spatrick // including Q).
rangeQuery(const size_t Q,std::vector<size_t> & Neighbors) const4209467b48Spatrick void InstructionBenchmarkClustering::rangeQuery(
4309467b48Spatrick     const size_t Q, std::vector<size_t> &Neighbors) const {
4409467b48Spatrick   Neighbors.clear();
4509467b48Spatrick   Neighbors.reserve(Points_.size() - 1); // The Q itself isn't a neighbor.
4609467b48Spatrick   const auto &QMeasurements = Points_[Q].Measurements;
4709467b48Spatrick   for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
4809467b48Spatrick     if (P == Q)
4909467b48Spatrick       continue;
5009467b48Spatrick     const auto &PMeasurements = Points_[P].Measurements;
5109467b48Spatrick     if (PMeasurements.empty()) // Error point.
5209467b48Spatrick       continue;
5309467b48Spatrick     if (isNeighbour(PMeasurements, QMeasurements,
5409467b48Spatrick                     AnalysisClusteringEpsilonSquared_)) {
5509467b48Spatrick       Neighbors.push_back(P);
5609467b48Spatrick     }
5709467b48Spatrick   }
5809467b48Spatrick }
5909467b48Spatrick 
6009467b48Spatrick // Given a set of points, checks that all the points are neighbours
6109467b48Spatrick // up to AnalysisClusteringEpsilon. This is O(2*N).
areAllNeighbours(ArrayRef<size_t> Pts) const6209467b48Spatrick bool InstructionBenchmarkClustering::areAllNeighbours(
6309467b48Spatrick     ArrayRef<size_t> Pts) const {
6409467b48Spatrick   // First, get the centroid of this group of points. This is O(N).
6509467b48Spatrick   SchedClassClusterCentroid G;
66*d415bd75Srobert   for (size_t P : Pts) {
6709467b48Spatrick     assert(P < Points_.size());
6809467b48Spatrick     ArrayRef<BenchmarkMeasure> Measurements = Points_[P].Measurements;
6909467b48Spatrick     if (Measurements.empty()) // Error point.
70*d415bd75Srobert       continue;
7109467b48Spatrick     G.addPoint(Measurements);
72*d415bd75Srobert   }
7309467b48Spatrick   const std::vector<BenchmarkMeasure> Centroid = G.getAsPoint();
7409467b48Spatrick 
7509467b48Spatrick   // Since we will be comparing with the centroid, we need to halve the epsilon.
7609467b48Spatrick   double AnalysisClusteringEpsilonHalvedSquared =
7709467b48Spatrick       AnalysisClusteringEpsilonSquared_ / 4.0;
7809467b48Spatrick 
7909467b48Spatrick   // And now check that every point is a neighbour of the centroid. Also O(N).
8009467b48Spatrick   return all_of(
8109467b48Spatrick       Pts, [this, &Centroid, AnalysisClusteringEpsilonHalvedSquared](size_t P) {
8209467b48Spatrick         assert(P < Points_.size());
8309467b48Spatrick         const auto &PMeasurements = Points_[P].Measurements;
8409467b48Spatrick         if (PMeasurements.empty()) // Error point.
8509467b48Spatrick           return true;             // Pretend that error point is a neighbour.
8609467b48Spatrick         return isNeighbour(PMeasurements, Centroid,
8709467b48Spatrick                            AnalysisClusteringEpsilonHalvedSquared);
8809467b48Spatrick       });
8909467b48Spatrick }
9009467b48Spatrick 
InstructionBenchmarkClustering(const std::vector<InstructionBenchmark> & Points,const double AnalysisClusteringEpsilonSquared)9109467b48Spatrick InstructionBenchmarkClustering::InstructionBenchmarkClustering(
9209467b48Spatrick     const std::vector<InstructionBenchmark> &Points,
9309467b48Spatrick     const double AnalysisClusteringEpsilonSquared)
9409467b48Spatrick     : Points_(Points),
9509467b48Spatrick       AnalysisClusteringEpsilonSquared_(AnalysisClusteringEpsilonSquared),
9609467b48Spatrick       NoiseCluster_(ClusterId::noise()), ErrorCluster_(ClusterId::error()) {}
9709467b48Spatrick 
validateAndSetup()9809467b48Spatrick Error InstructionBenchmarkClustering::validateAndSetup() {
9909467b48Spatrick   ClusterIdForPoint_.resize(Points_.size());
10009467b48Spatrick   // Mark erroneous measurements out.
10109467b48Spatrick   // All points must have the same number of dimensions, in the same order.
10209467b48Spatrick   const std::vector<BenchmarkMeasure> *LastMeasurement = nullptr;
10309467b48Spatrick   for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
10409467b48Spatrick     const auto &Point = Points_[P];
10509467b48Spatrick     if (!Point.Error.empty()) {
10609467b48Spatrick       ClusterIdForPoint_[P] = ClusterId::error();
10709467b48Spatrick       ErrorCluster_.PointIndices.push_back(P);
10809467b48Spatrick       continue;
10909467b48Spatrick     }
11009467b48Spatrick     const auto *CurMeasurement = &Point.Measurements;
11109467b48Spatrick     if (LastMeasurement) {
11209467b48Spatrick       if (LastMeasurement->size() != CurMeasurement->size()) {
113097a140dSpatrick         return make_error<ClusteringError>(
114097a140dSpatrick             "inconsistent measurement dimensions");
11509467b48Spatrick       }
11609467b48Spatrick       for (size_t I = 0, E = LastMeasurement->size(); I < E; ++I) {
11709467b48Spatrick         if (LastMeasurement->at(I).Key != CurMeasurement->at(I).Key) {
118097a140dSpatrick           return make_error<ClusteringError>(
119097a140dSpatrick               "inconsistent measurement dimensions keys");
12009467b48Spatrick         }
12109467b48Spatrick       }
12209467b48Spatrick     }
12309467b48Spatrick     LastMeasurement = CurMeasurement;
12409467b48Spatrick   }
12509467b48Spatrick   if (LastMeasurement) {
12609467b48Spatrick     NumDimensions_ = LastMeasurement->size();
12709467b48Spatrick   }
12809467b48Spatrick   return Error::success();
12909467b48Spatrick }
13009467b48Spatrick 
clusterizeDbScan(const size_t MinPts)13109467b48Spatrick void InstructionBenchmarkClustering::clusterizeDbScan(const size_t MinPts) {
13209467b48Spatrick   std::vector<size_t> Neighbors; // Persistent buffer to avoid allocs.
13309467b48Spatrick   for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
13409467b48Spatrick     if (!ClusterIdForPoint_[P].isUndef())
13509467b48Spatrick       continue; // Previously processed in inner loop.
13609467b48Spatrick     rangeQuery(P, Neighbors);
13709467b48Spatrick     if (Neighbors.size() + 1 < MinPts) { // Density check.
13809467b48Spatrick       // The region around P is not dense enough to create a new cluster, mark
13909467b48Spatrick       // as noise for now.
14009467b48Spatrick       ClusterIdForPoint_[P] = ClusterId::noise();
14109467b48Spatrick       continue;
14209467b48Spatrick     }
14309467b48Spatrick 
14409467b48Spatrick     // Create a new cluster, add P.
14509467b48Spatrick     Clusters_.emplace_back(ClusterId::makeValid(Clusters_.size()));
14609467b48Spatrick     Cluster &CurrentCluster = Clusters_.back();
14709467b48Spatrick     ClusterIdForPoint_[P] = CurrentCluster.Id; /* Label initial point */
14809467b48Spatrick     CurrentCluster.PointIndices.push_back(P);
14909467b48Spatrick 
15009467b48Spatrick     // Process P's neighbors.
15109467b48Spatrick     SetVector<size_t, std::deque<size_t>> ToProcess;
15209467b48Spatrick     ToProcess.insert(Neighbors.begin(), Neighbors.end());
15309467b48Spatrick     while (!ToProcess.empty()) {
15409467b48Spatrick       // Retrieve a point from the set.
15509467b48Spatrick       const size_t Q = *ToProcess.begin();
15609467b48Spatrick       ToProcess.erase(ToProcess.begin());
15709467b48Spatrick 
15809467b48Spatrick       if (ClusterIdForPoint_[Q].isNoise()) {
15909467b48Spatrick         // Change noise point to border point.
16009467b48Spatrick         ClusterIdForPoint_[Q] = CurrentCluster.Id;
16109467b48Spatrick         CurrentCluster.PointIndices.push_back(Q);
16209467b48Spatrick         continue;
16309467b48Spatrick       }
16409467b48Spatrick       if (!ClusterIdForPoint_[Q].isUndef()) {
16509467b48Spatrick         continue; // Previously processed.
16609467b48Spatrick       }
16709467b48Spatrick       // Add Q to the current custer.
16809467b48Spatrick       ClusterIdForPoint_[Q] = CurrentCluster.Id;
16909467b48Spatrick       CurrentCluster.PointIndices.push_back(Q);
17009467b48Spatrick       // And extend to the neighbors of Q if the region is dense enough.
17109467b48Spatrick       rangeQuery(Q, Neighbors);
17209467b48Spatrick       if (Neighbors.size() + 1 >= MinPts) {
17309467b48Spatrick         ToProcess.insert(Neighbors.begin(), Neighbors.end());
17409467b48Spatrick       }
17509467b48Spatrick     }
17609467b48Spatrick   }
17709467b48Spatrick   // assert(Neighbors.capacity() == (Points_.size() - 1));
17809467b48Spatrick   // ^ True, but it is not quaranteed to be true in all the cases.
17909467b48Spatrick 
18009467b48Spatrick   // Add noisy points to noise cluster.
18109467b48Spatrick   for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
18209467b48Spatrick     if (ClusterIdForPoint_[P].isNoise()) {
18309467b48Spatrick       NoiseCluster_.PointIndices.push_back(P);
18409467b48Spatrick     }
18509467b48Spatrick   }
18609467b48Spatrick }
18709467b48Spatrick 
clusterizeNaive(const MCSubtargetInfo & SubtargetInfo,const MCInstrInfo & InstrInfo)188*d415bd75Srobert void InstructionBenchmarkClustering::clusterizeNaive(
189*d415bd75Srobert     const MCSubtargetInfo &SubtargetInfo, const MCInstrInfo &InstrInfo) {
190*d415bd75Srobert   // Given an instruction Opcode, which sched class id's are represented,
191*d415bd75Srobert   // and which are the benchmarks for each sched class?
192*d415bd75Srobert   std::vector<SmallMapVector<unsigned, SmallVector<size_t, 1>, 1>>
193*d415bd75Srobert       OpcodeToSchedClassesToPoints;
194*d415bd75Srobert   const unsigned NumOpcodes = InstrInfo.getNumOpcodes();
195*d415bd75Srobert   OpcodeToSchedClassesToPoints.resize(NumOpcodes);
196*d415bd75Srobert   size_t NumClusters = 0;
19709467b48Spatrick   for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
19809467b48Spatrick     const InstructionBenchmark &Point = Points_[P];
199*d415bd75Srobert     const MCInst &MCI = Point.keyInstruction();
200*d415bd75Srobert     unsigned SchedClassId;
201*d415bd75Srobert     std::tie(SchedClassId, std::ignore) =
202*d415bd75Srobert         ResolvedSchedClass::resolveSchedClassId(SubtargetInfo, InstrInfo, MCI);
203*d415bd75Srobert     const unsigned Opcode = MCI.getOpcode();
20409467b48Spatrick     assert(Opcode < NumOpcodes && "NumOpcodes is incorrect (too small)");
205*d415bd75Srobert     auto &Points = OpcodeToSchedClassesToPoints[Opcode][SchedClassId];
206*d415bd75Srobert     if (Points.empty()) // If we previously have not seen any points of
207*d415bd75Srobert       ++NumClusters;    // this opcode's sched class, then new cluster begins.
208*d415bd75Srobert     Points.emplace_back(P);
20909467b48Spatrick   }
210*d415bd75Srobert   assert(NumClusters <= NumOpcodes &&
21109467b48Spatrick          "can't see more opcodes than there are total opcodes");
212*d415bd75Srobert   assert(NumClusters <= Points_.size() &&
21309467b48Spatrick          "can't see more opcodes than there are total points");
21409467b48Spatrick 
215*d415bd75Srobert   Clusters_.reserve(NumClusters); // We already know how many clusters there is.
216*d415bd75Srobert   for (const auto &SchedClassesOfOpcode : OpcodeToSchedClassesToPoints) {
217*d415bd75Srobert     if (SchedClassesOfOpcode.empty())
218*d415bd75Srobert       continue;
219*d415bd75Srobert     for (ArrayRef<size_t> PointsOfSchedClass :
220*d415bd75Srobert          make_second_range(SchedClassesOfOpcode)) {
221*d415bd75Srobert       if (PointsOfSchedClass.empty())
222*d415bd75Srobert         continue;
22309467b48Spatrick       // Create a new cluster.
22409467b48Spatrick       Clusters_.emplace_back(ClusterId::makeValid(
225*d415bd75Srobert           Clusters_.size(),
226*d415bd75Srobert           /*IsUnstable=*/!areAllNeighbours(PointsOfSchedClass)));
22709467b48Spatrick       Cluster &CurrentCluster = Clusters_.back();
22809467b48Spatrick       // Mark points as belonging to the new cluster.
229*d415bd75Srobert       for (size_t P : PointsOfSchedClass)
23009467b48Spatrick         ClusterIdForPoint_[P] = CurrentCluster.Id;
231*d415bd75Srobert       // And add all the points of this opcode's sched class to the new cluster.
232*d415bd75Srobert       CurrentCluster.PointIndices.reserve(PointsOfSchedClass.size());
233*d415bd75Srobert       CurrentCluster.PointIndices.assign(PointsOfSchedClass.begin(),
234*d415bd75Srobert                                          PointsOfSchedClass.end());
235*d415bd75Srobert       assert(CurrentCluster.PointIndices.size() == PointsOfSchedClass.size());
23609467b48Spatrick     }
237*d415bd75Srobert   }
238*d415bd75Srobert   assert(Clusters_.size() == NumClusters);
23909467b48Spatrick }
24009467b48Spatrick 
24109467b48Spatrick // Given an instruction Opcode, we can make benchmarks (measurements) of the
24209467b48Spatrick // instruction characteristics/performance. Then, to facilitate further analysis
24309467b48Spatrick // we group the benchmarks with *similar* characteristics into clusters.
24409467b48Spatrick // Now, this is all not entirely deterministic. Some instructions have variable
24509467b48Spatrick // characteristics, depending on their arguments. And thus, if we do several
24609467b48Spatrick // benchmarks of the same instruction Opcode, we may end up with *different*
24709467b48Spatrick // performance characteristics measurements. And when we then do clustering,
24809467b48Spatrick // these several benchmarks of the same instruction Opcode may end up being
24909467b48Spatrick // clustered into *different* clusters. This is not great for further analysis.
25009467b48Spatrick // We shall find every opcode with benchmarks not in just one cluster, and move
25109467b48Spatrick // *all* the benchmarks of said Opcode into one new unstable cluster per Opcode.
stabilize(unsigned NumOpcodes)25209467b48Spatrick void InstructionBenchmarkClustering::stabilize(unsigned NumOpcodes) {
25309467b48Spatrick   // Given an instruction Opcode and Config, in which clusters do benchmarks of
25409467b48Spatrick   // this instruction lie? Normally, they all should be in the same cluster.
25509467b48Spatrick   struct OpcodeAndConfig {
25609467b48Spatrick     explicit OpcodeAndConfig(const InstructionBenchmark &IB)
25709467b48Spatrick         : Opcode(IB.keyInstruction().getOpcode()), Config(&IB.Key.Config) {}
25809467b48Spatrick     unsigned Opcode;
25909467b48Spatrick     const std::string *Config;
26009467b48Spatrick 
26109467b48Spatrick     auto Tie() const -> auto { return std::tie(Opcode, *Config); }
26209467b48Spatrick 
26309467b48Spatrick     bool operator<(const OpcodeAndConfig &O) const { return Tie() < O.Tie(); }
26409467b48Spatrick     bool operator!=(const OpcodeAndConfig &O) const { return Tie() != O.Tie(); }
26509467b48Spatrick   };
26609467b48Spatrick   std::map<OpcodeAndConfig, SmallSet<ClusterId, 1>> OpcodeConfigToClusterIDs;
26709467b48Spatrick   // Populate OpcodeConfigToClusterIDs and UnstableOpcodes data structures.
26809467b48Spatrick   assert(ClusterIdForPoint_.size() == Points_.size() && "size mismatch");
26909467b48Spatrick   for (auto Point : zip(Points_, ClusterIdForPoint_)) {
27009467b48Spatrick     const ClusterId &ClusterIdOfPoint = std::get<1>(Point);
27109467b48Spatrick     if (!ClusterIdOfPoint.isValid())
27209467b48Spatrick       continue; // Only process fully valid clusters.
27309467b48Spatrick     const OpcodeAndConfig Key(std::get<0>(Point));
27409467b48Spatrick     SmallSet<ClusterId, 1> &ClusterIDsOfOpcode = OpcodeConfigToClusterIDs[Key];
27509467b48Spatrick     ClusterIDsOfOpcode.insert(ClusterIdOfPoint);
27609467b48Spatrick   }
27709467b48Spatrick 
27809467b48Spatrick   for (const auto &OpcodeConfigToClusterID : OpcodeConfigToClusterIDs) {
27909467b48Spatrick     const SmallSet<ClusterId, 1> &ClusterIDs = OpcodeConfigToClusterID.second;
28009467b48Spatrick     const OpcodeAndConfig &Key = OpcodeConfigToClusterID.first;
28109467b48Spatrick     // We only care about unstable instructions.
28209467b48Spatrick     if (ClusterIDs.size() < 2)
28309467b48Spatrick       continue;
28409467b48Spatrick 
28509467b48Spatrick     // Create a new unstable cluster, one per Opcode.
28609467b48Spatrick     Clusters_.emplace_back(ClusterId::makeValidUnstable(Clusters_.size()));
28709467b48Spatrick     Cluster &UnstableCluster = Clusters_.back();
28809467b48Spatrick     // We will find *at least* one point in each of these clusters.
28909467b48Spatrick     UnstableCluster.PointIndices.reserve(ClusterIDs.size());
29009467b48Spatrick 
29109467b48Spatrick     // Go through every cluster which we recorded as containing benchmarks
29209467b48Spatrick     // of this UnstableOpcode. NOTE: we only recorded valid clusters.
29309467b48Spatrick     for (const ClusterId &CID : ClusterIDs) {
29409467b48Spatrick       assert(CID.isValid() &&
29509467b48Spatrick              "We only recorded valid clusters, not noise/error clusters.");
29609467b48Spatrick       Cluster &OldCluster = Clusters_[CID.getId()]; // Valid clusters storage.
29709467b48Spatrick       // Within each cluster, go through each point, and either move it to the
29809467b48Spatrick       // new unstable cluster, or 'keep' it.
29909467b48Spatrick       // In this case, we'll reshuffle OldCluster.PointIndices vector
30009467b48Spatrick       // so that all the points that are *not* for UnstableOpcode are first,
30109467b48Spatrick       // and the rest of the points is for the UnstableOpcode.
30209467b48Spatrick       const auto it = std::stable_partition(
30309467b48Spatrick           OldCluster.PointIndices.begin(), OldCluster.PointIndices.end(),
30409467b48Spatrick           [this, &Key](size_t P) {
30509467b48Spatrick             return OpcodeAndConfig(Points_[P]) != Key;
30609467b48Spatrick           });
30709467b48Spatrick       assert(std::distance(it, OldCluster.PointIndices.end()) > 0 &&
30809467b48Spatrick              "Should have found at least one bad point");
30909467b48Spatrick       // Mark to-be-moved points as belonging to the new cluster.
31009467b48Spatrick       std::for_each(it, OldCluster.PointIndices.end(),
31109467b48Spatrick                     [this, &UnstableCluster](size_t P) {
31209467b48Spatrick                       ClusterIdForPoint_[P] = UnstableCluster.Id;
31309467b48Spatrick                     });
31409467b48Spatrick       // Actually append to-be-moved points to the new cluster.
31509467b48Spatrick       UnstableCluster.PointIndices.insert(UnstableCluster.PointIndices.end(),
31609467b48Spatrick                                           it, OldCluster.PointIndices.end());
317*d415bd75Srobert       // And finally, remove "to-be-moved" points from the old cluster.
31809467b48Spatrick       OldCluster.PointIndices.erase(it, OldCluster.PointIndices.end());
31909467b48Spatrick       // Now, the old cluster may end up being empty, but let's just keep it
32009467b48Spatrick       // in whatever state it ended up. Purging empty clusters isn't worth it.
32109467b48Spatrick     };
32209467b48Spatrick     assert(UnstableCluster.PointIndices.size() > 1 &&
32309467b48Spatrick            "New unstable cluster should end up with more than one point.");
32409467b48Spatrick     assert(UnstableCluster.PointIndices.size() >= ClusterIDs.size() &&
32509467b48Spatrick            "New unstable cluster should end up with no less points than there "
32609467b48Spatrick            "was clusters");
32709467b48Spatrick   }
32809467b48Spatrick }
32909467b48Spatrick 
create(const std::vector<InstructionBenchmark> & Points,const ModeE Mode,const size_t DbscanMinPts,const double AnalysisClusteringEpsilon,const MCSubtargetInfo * SubtargetInfo,const MCInstrInfo * InstrInfo)33009467b48Spatrick Expected<InstructionBenchmarkClustering> InstructionBenchmarkClustering::create(
33109467b48Spatrick     const std::vector<InstructionBenchmark> &Points, const ModeE Mode,
33209467b48Spatrick     const size_t DbscanMinPts, const double AnalysisClusteringEpsilon,
333*d415bd75Srobert     const MCSubtargetInfo *SubtargetInfo, const MCInstrInfo *InstrInfo) {
33409467b48Spatrick   InstructionBenchmarkClustering Clustering(
33509467b48Spatrick       Points, AnalysisClusteringEpsilon * AnalysisClusteringEpsilon);
33609467b48Spatrick   if (auto Error = Clustering.validateAndSetup()) {
33709467b48Spatrick     return std::move(Error);
33809467b48Spatrick   }
33909467b48Spatrick   if (Clustering.ErrorCluster_.PointIndices.size() == Points.size()) {
34009467b48Spatrick     return Clustering; // Nothing to cluster.
34109467b48Spatrick   }
34209467b48Spatrick 
34309467b48Spatrick   if (Mode == ModeE::Dbscan) {
34409467b48Spatrick     Clustering.clusterizeDbScan(DbscanMinPts);
34509467b48Spatrick 
346*d415bd75Srobert     if (InstrInfo)
347*d415bd75Srobert       Clustering.stabilize(InstrInfo->getNumOpcodes());
34809467b48Spatrick   } else /*if(Mode == ModeE::Naive)*/ {
349*d415bd75Srobert     if (!SubtargetInfo || !InstrInfo)
350*d415bd75Srobert       return make_error<Failure>("'naive' clustering mode requires "
351*d415bd75Srobert                                  "SubtargetInfo and InstrInfo to be present");
352*d415bd75Srobert     Clustering.clusterizeNaive(*SubtargetInfo, *InstrInfo);
35309467b48Spatrick   }
35409467b48Spatrick 
35509467b48Spatrick   return Clustering;
35609467b48Spatrick }
35709467b48Spatrick 
addPoint(ArrayRef<BenchmarkMeasure> Point)35809467b48Spatrick void SchedClassClusterCentroid::addPoint(ArrayRef<BenchmarkMeasure> Point) {
35909467b48Spatrick   if (Representative.empty())
36009467b48Spatrick     Representative.resize(Point.size());
36109467b48Spatrick   assert(Representative.size() == Point.size() &&
36209467b48Spatrick          "All points should have identical dimensions.");
36309467b48Spatrick 
36409467b48Spatrick   for (auto I : zip(Representative, Point))
36509467b48Spatrick     std::get<0>(I).push(std::get<1>(I));
36609467b48Spatrick }
36709467b48Spatrick 
getAsPoint() const36809467b48Spatrick std::vector<BenchmarkMeasure> SchedClassClusterCentroid::getAsPoint() const {
36909467b48Spatrick   std::vector<BenchmarkMeasure> ClusterCenterPoint(Representative.size());
37009467b48Spatrick   for (auto I : zip(ClusterCenterPoint, Representative))
37109467b48Spatrick     std::get<0>(I).PerInstructionValue = std::get<1>(I).avg();
37209467b48Spatrick   return ClusterCenterPoint;
37309467b48Spatrick }
37409467b48Spatrick 
validate(InstructionBenchmark::ModeE Mode) const37509467b48Spatrick bool SchedClassClusterCentroid::validate(
37609467b48Spatrick     InstructionBenchmark::ModeE Mode) const {
37709467b48Spatrick   size_t NumMeasurements = Representative.size();
37809467b48Spatrick   switch (Mode) {
37909467b48Spatrick   case InstructionBenchmark::Latency:
38009467b48Spatrick     if (NumMeasurements != 1) {
38109467b48Spatrick       errs()
38209467b48Spatrick           << "invalid number of measurements in latency mode: expected 1, got "
38309467b48Spatrick           << NumMeasurements << "\n";
38409467b48Spatrick       return false;
38509467b48Spatrick     }
38609467b48Spatrick     break;
38709467b48Spatrick   case InstructionBenchmark::Uops:
38809467b48Spatrick     // Can have many measurements.
38909467b48Spatrick     break;
39009467b48Spatrick   case InstructionBenchmark::InverseThroughput:
39109467b48Spatrick     if (NumMeasurements != 1) {
39209467b48Spatrick       errs() << "invalid number of measurements in inverse throughput "
39309467b48Spatrick                 "mode: expected 1, got "
39409467b48Spatrick              << NumMeasurements << "\n";
39509467b48Spatrick       return false;
39609467b48Spatrick     }
39709467b48Spatrick     break;
39809467b48Spatrick   default:
39909467b48Spatrick     llvm_unreachable("unimplemented measurement matching mode");
40009467b48Spatrick     return false;
40109467b48Spatrick   }
40209467b48Spatrick 
40309467b48Spatrick   return true; // All good.
40409467b48Spatrick }
40509467b48Spatrick 
40609467b48Spatrick } // namespace exegesis
40709467b48Spatrick } // namespace llvm
408