#include "Clustering.h"
#include "Error.h"
#include "SchedClassResolution.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include <algorithm>
#include <deque>
#include <string>
#include <vector>
namespace llvm {
namespace exegesis {
void InstructionBenchmarkClustering::rangeQuery(
const size_t Q, std::vector<size_t> &Neighbors) const {
Neighbors.clear();
Neighbors.reserve(Points_.size() - 1); const auto &QMeasurements = Points_[Q].Measurements;
for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
if (P == Q)
continue;
const auto &PMeasurements = Points_[P].Measurements;
if (PMeasurements.empty()) continue;
if (isNeighbour(PMeasurements, QMeasurements,
AnalysisClusteringEpsilonSquared_)) {
Neighbors.push_back(P);
}
}
}
bool InstructionBenchmarkClustering::areAllNeighbours(
ArrayRef<size_t> Pts) const {
SchedClassClusterCentroid G;
for_each(Pts, [this, &G](size_t P) {
assert(P < Points_.size());
ArrayRef<BenchmarkMeasure> Measurements = Points_[P].Measurements;
if (Measurements.empty()) return;
G.addPoint(Measurements);
});
const std::vector<BenchmarkMeasure> Centroid = G.getAsPoint();
double AnalysisClusteringEpsilonHalvedSquared =
AnalysisClusteringEpsilonSquared_ / 4.0;
return all_of(
Pts, [this, &Centroid, AnalysisClusteringEpsilonHalvedSquared](size_t P) {
assert(P < Points_.size());
const auto &PMeasurements = Points_[P].Measurements;
if (PMeasurements.empty()) return true; return isNeighbour(PMeasurements, Centroid,
AnalysisClusteringEpsilonHalvedSquared);
});
}
InstructionBenchmarkClustering::InstructionBenchmarkClustering(
const std::vector<InstructionBenchmark> &Points,
const double AnalysisClusteringEpsilonSquared)
: Points_(Points),
AnalysisClusteringEpsilonSquared_(AnalysisClusteringEpsilonSquared),
NoiseCluster_(ClusterId::noise()), ErrorCluster_(ClusterId::error()) {}
Error InstructionBenchmarkClustering::validateAndSetup() {
ClusterIdForPoint_.resize(Points_.size());
const std::vector<BenchmarkMeasure> *LastMeasurement = nullptr;
for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
const auto &Point = Points_[P];
if (!Point.Error.empty()) {
ClusterIdForPoint_[P] = ClusterId::error();
ErrorCluster_.PointIndices.push_back(P);
continue;
}
const auto *CurMeasurement = &Point.Measurements;
if (LastMeasurement) {
if (LastMeasurement->size() != CurMeasurement->size()) {
return make_error<ClusteringError>(
"inconsistent measurement dimensions");
}
for (size_t I = 0, E = LastMeasurement->size(); I < E; ++I) {
if (LastMeasurement->at(I).Key != CurMeasurement->at(I).Key) {
return make_error<ClusteringError>(
"inconsistent measurement dimensions keys");
}
}
}
LastMeasurement = CurMeasurement;
}
if (LastMeasurement) {
NumDimensions_ = LastMeasurement->size();
}
return Error::success();
}
void InstructionBenchmarkClustering::clusterizeDbScan(const size_t MinPts) {
std::vector<size_t> Neighbors; for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
if (!ClusterIdForPoint_[P].isUndef())
continue; rangeQuery(P, Neighbors);
if (Neighbors.size() + 1 < MinPts) { ClusterIdForPoint_[P] = ClusterId::noise();
continue;
}
Clusters_.emplace_back(ClusterId::makeValid(Clusters_.size()));
Cluster &CurrentCluster = Clusters_.back();
ClusterIdForPoint_[P] = CurrentCluster.Id;
CurrentCluster.PointIndices.push_back(P);
SetVector<size_t, std::deque<size_t>> ToProcess;
ToProcess.insert(Neighbors.begin(), Neighbors.end());
while (!ToProcess.empty()) {
const size_t Q = *ToProcess.begin();
ToProcess.erase(ToProcess.begin());
if (ClusterIdForPoint_[Q].isNoise()) {
ClusterIdForPoint_[Q] = CurrentCluster.Id;
CurrentCluster.PointIndices.push_back(Q);
continue;
}
if (!ClusterIdForPoint_[Q].isUndef()) {
continue; }
ClusterIdForPoint_[Q] = CurrentCluster.Id;
CurrentCluster.PointIndices.push_back(Q);
rangeQuery(Q, Neighbors);
if (Neighbors.size() + 1 >= MinPts) {
ToProcess.insert(Neighbors.begin(), Neighbors.end());
}
}
}
for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
if (ClusterIdForPoint_[P].isNoise()) {
NoiseCluster_.PointIndices.push_back(P);
}
}
}
void InstructionBenchmarkClustering::clusterizeNaive(
const MCSubtargetInfo &SubtargetInfo, const MCInstrInfo &InstrInfo) {
std::vector<SmallMapVector<unsigned, SmallVector<size_t, 1>, 1>>
OpcodeToSchedClassesToPoints;
const unsigned NumOpcodes = InstrInfo.getNumOpcodes();
OpcodeToSchedClassesToPoints.resize(NumOpcodes);
size_t NumClusters = 0;
for (size_t P = 0, NumPoints = Points_.size(); P < NumPoints; ++P) {
const InstructionBenchmark &Point = Points_[P];
const MCInst &MCI = Point.keyInstruction();
unsigned SchedClassId;
std::tie(SchedClassId, std::ignore) =
ResolvedSchedClass::resolveSchedClassId(SubtargetInfo, InstrInfo, MCI);
const unsigned Opcode = MCI.getOpcode();
assert(Opcode < NumOpcodes && "NumOpcodes is incorrect (too small)");
auto &Points = OpcodeToSchedClassesToPoints[Opcode][SchedClassId];
if (Points.empty()) ++NumClusters; Points.emplace_back(P);
}
assert(NumClusters <= NumOpcodes &&
"can't see more opcodes than there are total opcodes");
assert(NumClusters <= Points_.size() &&
"can't see more opcodes than there are total points");
Clusters_.reserve(NumClusters); for (const auto &SchedClassesOfOpcode : OpcodeToSchedClassesToPoints) {
if (SchedClassesOfOpcode.empty())
continue;
for (ArrayRef<size_t> PointsOfSchedClass :
make_second_range(SchedClassesOfOpcode)) {
if (PointsOfSchedClass.empty())
continue;
Clusters_.emplace_back(ClusterId::makeValid(
Clusters_.size(),
!areAllNeighbours(PointsOfSchedClass)));
Cluster &CurrentCluster = Clusters_.back();
for_each(PointsOfSchedClass, [this, &CurrentCluster](size_t P) {
ClusterIdForPoint_[P] = CurrentCluster.Id;
});
CurrentCluster.PointIndices.reserve(PointsOfSchedClass.size());
CurrentCluster.PointIndices.assign(PointsOfSchedClass.begin(),
PointsOfSchedClass.end());
assert(CurrentCluster.PointIndices.size() == PointsOfSchedClass.size());
}
}
assert(Clusters_.size() == NumClusters);
}
void InstructionBenchmarkClustering::stabilize(unsigned NumOpcodes) {
struct OpcodeAndConfig {
explicit OpcodeAndConfig(const InstructionBenchmark &IB)
: Opcode(IB.keyInstruction().getOpcode()), Config(&IB.Key.Config) {}
unsigned Opcode;
const std::string *Config;
auto Tie() const -> auto { return std::tie(Opcode, *Config); }
bool operator<(const OpcodeAndConfig &O) const { return Tie() < O.Tie(); }
bool operator!=(const OpcodeAndConfig &O) const { return Tie() != O.Tie(); }
};
std::map<OpcodeAndConfig, SmallSet<ClusterId, 1>> OpcodeConfigToClusterIDs;
assert(ClusterIdForPoint_.size() == Points_.size() && "size mismatch");
for (auto Point : zip(Points_, ClusterIdForPoint_)) {
const ClusterId &ClusterIdOfPoint = std::get<1>(Point);
if (!ClusterIdOfPoint.isValid())
continue; const OpcodeAndConfig Key(std::get<0>(Point));
SmallSet<ClusterId, 1> &ClusterIDsOfOpcode = OpcodeConfigToClusterIDs[Key];
ClusterIDsOfOpcode.insert(ClusterIdOfPoint);
}
for (const auto &OpcodeConfigToClusterID : OpcodeConfigToClusterIDs) {
const SmallSet<ClusterId, 1> &ClusterIDs = OpcodeConfigToClusterID.second;
const OpcodeAndConfig &Key = OpcodeConfigToClusterID.first;
if (ClusterIDs.size() < 2)
continue;
Clusters_.emplace_back(ClusterId::makeValidUnstable(Clusters_.size()));
Cluster &UnstableCluster = Clusters_.back();
UnstableCluster.PointIndices.reserve(ClusterIDs.size());
for (const ClusterId &CID : ClusterIDs) {
assert(CID.isValid() &&
"We only recorded valid clusters, not noise/error clusters.");
Cluster &OldCluster = Clusters_[CID.getId()]; const auto it = std::stable_partition(
OldCluster.PointIndices.begin(), OldCluster.PointIndices.end(),
[this, &Key](size_t P) {
return OpcodeAndConfig(Points_[P]) != Key;
});
assert(std::distance(it, OldCluster.PointIndices.end()) > 0 &&
"Should have found at least one bad point");
std::for_each(it, OldCluster.PointIndices.end(),
[this, &UnstableCluster](size_t P) {
ClusterIdForPoint_[P] = UnstableCluster.Id;
});
UnstableCluster.PointIndices.insert(UnstableCluster.PointIndices.end(),
it, OldCluster.PointIndices.end());
OldCluster.PointIndices.erase(it, OldCluster.PointIndices.end());
};
assert(UnstableCluster.PointIndices.size() > 1 &&
"New unstable cluster should end up with more than one point.");
assert(UnstableCluster.PointIndices.size() >= ClusterIDs.size() &&
"New unstable cluster should end up with no less points than there "
"was clusters");
}
}
Expected<InstructionBenchmarkClustering> InstructionBenchmarkClustering::create(
const std::vector<InstructionBenchmark> &Points, const ModeE Mode,
const size_t DbscanMinPts, const double AnalysisClusteringEpsilon,
const MCSubtargetInfo *SubtargetInfo, const MCInstrInfo *InstrInfo) {
InstructionBenchmarkClustering Clustering(
Points, AnalysisClusteringEpsilon * AnalysisClusteringEpsilon);
if (auto Error = Clustering.validateAndSetup()) {
return std::move(Error);
}
if (Clustering.ErrorCluster_.PointIndices.size() == Points.size()) {
return Clustering; }
if (Mode == ModeE::Dbscan) {
Clustering.clusterizeDbScan(DbscanMinPts);
if (InstrInfo)
Clustering.stabilize(InstrInfo->getNumOpcodes());
} else {
if (!SubtargetInfo || !InstrInfo)
return make_error<Failure>("'naive' clustering mode requires "
"SubtargetInfo and InstrInfo to be present");
Clustering.clusterizeNaive(*SubtargetInfo, *InstrInfo);
}
return Clustering;
}
void SchedClassClusterCentroid::addPoint(ArrayRef<BenchmarkMeasure> Point) {
if (Representative.empty())
Representative.resize(Point.size());
assert(Representative.size() == Point.size() &&
"All points should have identical dimensions.");
for (auto I : zip(Representative, Point))
std::get<0>(I).push(std::get<1>(I));
}
std::vector<BenchmarkMeasure> SchedClassClusterCentroid::getAsPoint() const {
std::vector<BenchmarkMeasure> ClusterCenterPoint(Representative.size());
for (auto I : zip(ClusterCenterPoint, Representative))
std::get<0>(I).PerInstructionValue = std::get<1>(I).avg();
return ClusterCenterPoint;
}
bool SchedClassClusterCentroid::validate(
InstructionBenchmark::ModeE Mode) const {
size_t NumMeasurements = Representative.size();
switch (Mode) {
case InstructionBenchmark::Latency:
if (NumMeasurements != 1) {
errs()
<< "invalid number of measurements in latency mode: expected 1, got "
<< NumMeasurements << "\n";
return false;
}
break;
case InstructionBenchmark::Uops:
break;
case InstructionBenchmark::InverseThroughput:
if (NumMeasurements != 1) {
errs() << "invalid number of measurements in inverse throughput "
"mode: expected 1, got "
<< NumMeasurements << "\n";
return false;
}
break;
default:
llvm_unreachable("unimplemented measurement matching mode");
return false;
}
return true; }
} }