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