[llvm-exegesis] Allow measuring several instructions in a single run.

Summary:
We try to recover gracefully on instructions that would crash the
program.

This includes some refactoring of runMeasurement() implementations.

Reviewers: gchatelet

Subscribers: tschuett, llvm-commits

Differential Revision: https://reviews.llvm.org/D53371

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@344695 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Clement Courbet 2018-10-17 15:04:15 +00:00
parent 0cb92ac202
commit 0a1aef0095
8 changed files with 160 additions and 86 deletions

View File

@ -175,9 +175,10 @@ OPTIONS
Specify the opcode to measure, by index. See example 1 for details.
Either `opcode-index`, `opcode-name` or `snippets-file` must be set.
.. option:: -opcode-name=<LLVM opcode name>
.. option:: -opcode-name=<opcode name 1>,<opcode name 2>,...
Specify the opcode to measure, by name. See example 1 for details.
Specify the opcode to measure, by name. Several opcodes can be specified as
a comma-separated list. See example 1 for details.
Either `opcode-index`, `opcode-name` or `snippets-file` must be set.
.. option:: -snippets-file=<filename>

View File

@ -13,9 +13,11 @@
#include "Assembler.h"
#include "BenchmarkRunner.h"
#include "MCInstrDescView.h"
#include "PerfHelper.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/CrashRecoveryContext.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Program.h"
@ -43,6 +45,54 @@ GenerateInstructions(const BenchmarkCode &BC, const size_t MinInstructions) {
return Code;
}
namespace {
class FunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor {
public:
FunctionExecutorImpl(const LLVMState &State,
llvm::object::OwningBinary<llvm::object::ObjectFile> Obj,
BenchmarkRunner::ScratchSpace *Scratch)
: Function(State.createTargetMachine(), std::move(Obj)),
Scratch(Scratch) {}
private:
llvm::Expected<int64_t> runAndMeasure(const char *Counters) const override {
// We sum counts when there are several counters for a single ProcRes
// (e.g. P23 on SandyBridge).
int64_t CounterValue = 0;
llvm::SmallVector<llvm::StringRef, 2> CounterNames;
llvm::StringRef(Counters).split(CounterNames, ',');
char *const ScratchPtr = Scratch->ptr();
for (const auto &CounterName : CounterNames) {
pfm::PerfEvent PerfEvent(CounterName);
if (!PerfEvent.valid())
llvm::report_fatal_error(
llvm::Twine("invalid perf event ").concat(Counters));
pfm::Counter Counter(PerfEvent);
Scratch->clear();
{
llvm::CrashRecoveryContext CRC;
llvm::CrashRecoveryContext::Enable();
const bool Crashed = !CRC.RunSafely([this, &Counter, ScratchPtr]() {
Counter.start();
Function(ScratchPtr);
Counter.stop();
});
llvm::CrashRecoveryContext::Disable();
// FIXME: Better diagnosis.
if (Crashed)
return llvm::make_error<BenchmarkFailure>(
"snippet crashed while running");
}
CounterValue += Counter.read();
}
return CounterValue;
}
const ExecutableFunction Function;
BenchmarkRunner::ScratchSpace *const Scratch;
};
} // namespace
InstructionBenchmark
BenchmarkRunner::runConfiguration(const BenchmarkCode &BC,
unsigned NumRepetitions) const {
@ -86,16 +136,21 @@ BenchmarkRunner::runConfiguration(const BenchmarkCode &BC,
}
llvm::outs() << "Check generated assembly with: /usr/bin/objdump -d "
<< *ObjectFilePath << "\n";
const ExecutableFunction EF(State.createTargetMachine(),
getObjectFromFile(*ObjectFilePath));
InstrBenchmark.Measurements = runMeasurements(EF, *Scratch);
const FunctionExecutorImpl Executor(State, getObjectFromFile(*ObjectFilePath),
Scratch.get());
auto Measurements = runMeasurements(Executor);
if (llvm::Error E = Measurements.takeError()) {
InstrBenchmark.Error = llvm::toString(std::move(E));
return InstrBenchmark;
}
InstrBenchmark.Measurements = std::move(*Measurements);
assert(InstrBenchmark.NumRepetitions > 0 && "invalid NumRepetitions");
for (BenchmarkMeasure &BM : InstrBenchmark.Measurements) {
// Scale the measurements by instruction.
BM.PerInstructionValue /= InstrBenchmark.NumRepetitions;
// Scale the measurements by snippet.
BM.PerSnippetValue *= static_cast<double>(BC.Instructions.size()) /
InstrBenchmark.NumRepetitions;
InstrBenchmark.NumRepetitions;
}
return InstrBenchmark;
@ -115,4 +170,6 @@ BenchmarkRunner::writeObjectFile(const BenchmarkCode &BC,
return ResultPath.str();
}
BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {}
} // namespace exegesis

View File

@ -64,13 +64,21 @@ public:
char *const AlignedPtr;
};
// A helper to measure counters while executing a function in a sandboxed
// context.
class FunctionExecutor {
public:
~FunctionExecutor();
virtual llvm::Expected<int64_t>
runAndMeasure(const char *Counters) const = 0;
};
protected:
const LLVMState &State;
private:
virtual std::vector<BenchmarkMeasure>
runMeasurements(const ExecutableFunction &EF,
ScratchSpace &Scratch) const = 0;
virtual llvm::Expected<std::vector<BenchmarkMeasure>>
runMeasurements(const FunctionExecutor &Executor) const = 0;
llvm::Expected<std::string>
writeObjectFile(const BenchmarkCode &Configuration,

View File

@ -12,7 +12,6 @@
#include "Assembler.h"
#include "BenchmarkRunner.h"
#include "MCInstrDescView.h"
#include "PerfHelper.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstBuilder.h"
@ -178,30 +177,26 @@ const char *LatencyBenchmarkRunner::getCounterName() const {
LatencyBenchmarkRunner::~LatencyBenchmarkRunner() = default;
std::vector<BenchmarkMeasure>
LatencyBenchmarkRunner::runMeasurements(const ExecutableFunction &Function,
ScratchSpace &Scratch) const {
llvm::Expected<std::vector<BenchmarkMeasure>>
LatencyBenchmarkRunner::runMeasurements(
const FunctionExecutor &Executor) const {
// Cycle measurements include some overhead from the kernel. Repeat the
// measure several times and take the minimum value.
constexpr const int NumMeasurements = 30;
int64_t MinLatency = std::numeric_limits<int64_t>::max();
int64_t MinValue = std::numeric_limits<int64_t>::max();
const char *CounterName = getCounterName();
if (!CounterName)
llvm::report_fatal_error("could not determine cycle counter name");
const pfm::PerfEvent CyclesPerfEvent(CounterName);
if (!CyclesPerfEvent.valid())
llvm::report_fatal_error("invalid perf event");
for (size_t I = 0; I < NumMeasurements; ++I) {
pfm::Counter Counter(CyclesPerfEvent);
Scratch.clear();
Counter.start();
Function(Scratch.ptr());
Counter.stop();
const int64_t Value = Counter.read();
if (Value < MinLatency)
MinLatency = Value;
auto ExpectedCounterValue = Executor.runAndMeasure(CounterName);
if (!ExpectedCounterValue)
return ExpectedCounterValue.takeError();
if (*ExpectedCounterValue < MinValue)
MinValue = *ExpectedCounterValue;
}
return {BenchmarkMeasure::Create("latency", MinLatency)};
std::vector<BenchmarkMeasure> Result = {
BenchmarkMeasure::Create("latency", MinValue)};
return std::move(Result);
}
} // namespace exegesis

View File

@ -37,9 +37,8 @@ public:
~LatencyBenchmarkRunner() override;
private:
std::vector<BenchmarkMeasure>
runMeasurements(const ExecutableFunction &EF,
ScratchSpace &Scratch) const override;
llvm::Expected<std::vector<BenchmarkMeasure>>
runMeasurements(const FunctionExecutor &Executor) const override;
virtual const char *getCounterName() const;
};

View File

@ -12,7 +12,6 @@
#include "Assembler.h"
#include "BenchmarkRunner.h"
#include "MCInstrDescView.h"
#include "PerfHelper.h"
#include "Target.h"
// FIXME: Load constants into registers (e.g. with fld1) to not break
@ -221,33 +220,10 @@ UopsSnippetGenerator::generateCodeTemplates(const Instruction &Instr) const {
return getSingleton(std::move(CT));
}
std::vector<BenchmarkMeasure>
UopsBenchmarkRunner::runMeasurements(const ExecutableFunction &Function,
ScratchSpace &Scratch) const {
llvm::Expected<std::vector<BenchmarkMeasure>>
UopsBenchmarkRunner::runMeasurements(const FunctionExecutor &Executor) const {
const auto &SchedModel = State.getSubtargetInfo().getSchedModel();
const auto RunMeasurement = [&Function,
&Scratch](const char *const Counters) {
// We sum counts when there are several counters for a single ProcRes
// (e.g. P23 on SandyBridge).
int64_t CounterValue = 0;
llvm::SmallVector<llvm::StringRef, 2> CounterNames;
llvm::StringRef(Counters).split(CounterNames, ',');
for (const auto &CounterName : CounterNames) {
pfm::PerfEvent UopPerfEvent(CounterName);
if (!UopPerfEvent.valid())
llvm::report_fatal_error(
llvm::Twine("invalid perf event ").concat(Counters));
pfm::Counter Counter(UopPerfEvent);
Scratch.clear();
Counter.start();
Function(Scratch.ptr());
Counter.stop();
CounterValue += Counter.read();
}
return CounterValue;
};
std::vector<BenchmarkMeasure> Result;
const auto &PfmCounters = SchedModel.getExtraProcessorInfo().PfmCounters;
// Uops per port.
@ -256,16 +232,21 @@ UopsBenchmarkRunner::runMeasurements(const ExecutableFunction &Function,
const char *const Counters = PfmCounters.IssueCounters[ProcResIdx];
if (!Counters)
continue;
const double CounterValue = RunMeasurement(Counters);
auto ExpectedCounterValue = Executor.runAndMeasure(Counters);
if (!ExpectedCounterValue)
return ExpectedCounterValue.takeError();
Result.push_back(BenchmarkMeasure::Create(
SchedModel.getProcResource(ProcResIdx)->Name, CounterValue));
SchedModel.getProcResource(ProcResIdx)->Name, *ExpectedCounterValue));
}
// NumMicroOps.
if (const char *const UopsCounter = PfmCounters.UopsCounter) {
const double CounterValue = RunMeasurement(UopsCounter);
Result.push_back(BenchmarkMeasure::Create("NumMicroOps", CounterValue));
auto ExpectedCounterValue = Executor.runAndMeasure(UopsCounter);
if (!ExpectedCounterValue)
return ExpectedCounterValue.takeError();
Result.push_back(
BenchmarkMeasure::Create("NumMicroOps", *ExpectedCounterValue));
}
return Result;
return std::move(Result);
}
constexpr const size_t UopsSnippetGenerator::kMinNumDifferentAddresses;

View File

@ -68,9 +68,8 @@ public:
static constexpr const size_t kMinNumDifferentAddresses = 6;
private:
std::vector<BenchmarkMeasure>
runMeasurements(const ExecutableFunction &EF,
ScratchSpace &Scratch) const override;
llvm::Expected<std::vector<BenchmarkMeasure>>
runMeasurements(const FunctionExecutor &Executor) const override;
};
} // namespace exegesis

View File

@ -38,13 +38,14 @@
#include <algorithm>
#include <string>
static llvm::cl::opt<unsigned>
static llvm::cl::opt<int>
OpcodeIndex("opcode-index", llvm::cl::desc("opcode to measure, by index"),
llvm::cl::init(0));
static llvm::cl::opt<std::string>
OpcodeName("opcode-name", llvm::cl::desc("opcode to measure, by name"),
llvm::cl::init(""));
static llvm::cl::opt<std::string> OpcodeNames(
"opcode-name",
llvm::cl::desc("comma-separated list of opcodes to measure, by name"),
llvm::cl::init(""));
static llvm::cl::opt<std::string>
SnippetsFile("snippets-file", llvm::cl::desc("code snippets to measure"),
@ -99,11 +100,12 @@ static llvm::ExitOnError ExitOnErr;
void LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET();
#endif
// Checks that only one of OpcodeName, OpcodeIndex or SnippetsFile is provided,
// and returns the opcode index or 0 if snippets should be read from
// Checks that only one of OpcodeNames, OpcodeIndex or SnippetsFile is provided,
// and returns the opcode indices or {} if snippets should be read from
// `SnippetsFile`.
static unsigned getOpcodeOrDie(const llvm::MCInstrInfo &MCInstrInfo) {
const size_t NumSetFlags = (OpcodeName.empty() ? 0 : 1) +
static std::vector<unsigned>
getOpcodesOrDie(const llvm::MCInstrInfo &MCInstrInfo) {
const size_t NumSetFlags = (OpcodeNames.empty() ? 0 : 1) +
(OpcodeIndex == 0 ? 0 : 1) +
(SnippetsFile.empty() ? 0 : 1);
if (NumSetFlags != 1)
@ -111,14 +113,35 @@ static unsigned getOpcodeOrDie(const llvm::MCInstrInfo &MCInstrInfo) {
"please provide one and only one of 'opcode-index', 'opcode-name' or "
"'snippets-file'");
if (!SnippetsFile.empty())
return 0;
return {};
if (OpcodeIndex > 0)
return OpcodeIndex;
return {static_cast<unsigned>(OpcodeIndex)};
if (OpcodeIndex < 0) {
std::vector<unsigned> Result;
for (unsigned I = 1, E = MCInstrInfo.getNumOpcodes(); I <= E; ++I)
Result.push_back(I);
return Result;
}
// Resolve opcode name -> opcode.
for (unsigned I = 0, E = MCInstrInfo.getNumOpcodes(); I < E; ++I)
if (MCInstrInfo.getName(I) == OpcodeName)
return I;
llvm::report_fatal_error(llvm::Twine("unknown opcode ").concat(OpcodeName));
const auto ResolveName =
[&MCInstrInfo](llvm::StringRef OpcodeName) -> unsigned {
for (unsigned I = 1, E = MCInstrInfo.getNumOpcodes(); I < E; ++I)
if (MCInstrInfo.getName(I) == OpcodeName)
return I;
return 0u;
};
llvm::SmallVector<llvm::StringRef, 2> Pieces;
llvm::StringRef(OpcodeNames.getValue())
.split(Pieces, ",", /* MaxSplit */ -1, /* KeepEmpty */ false);
std::vector<unsigned> Result;
for (const llvm::StringRef OpcodeName : Pieces) {
if (unsigned Opcode = ResolveName(OpcodeName))
Result.push_back(Opcode);
else
llvm::report_fatal_error(
llvm::Twine("unknown opcode ").concat(OpcodeName));
}
return Result;
}
// Generates code snippets for opcode `Opcode`.
@ -299,18 +322,29 @@ void benchmarkMain() {
#endif
const LLVMState State;
const auto Opcode = getOpcodeOrDie(State.getInstrInfo());
const auto Opcodes = getOpcodesOrDie(State.getInstrInfo());
std::vector<BenchmarkCode> Configurations;
if (Opcode > 0) {
// Ignore instructions without a sched class if -ignore-invalid-sched-class
// is passed.
if (IgnoreInvalidSchedClass &&
State.getInstrInfo().get(Opcode).getSchedClass() == 0) {
llvm::errs() << "ignoring instruction without sched class\n";
return;
if (!Opcodes.empty()) {
for (const unsigned Opcode : Opcodes) {
// Ignore instructions without a sched class if
// -ignore-invalid-sched-class is passed.
if (IgnoreInvalidSchedClass &&
State.getInstrInfo().get(Opcode).getSchedClass() == 0) {
llvm::errs() << State.getInstrInfo().getName(Opcode)
<< ": ignoring instruction without sched class\n";
continue;
}
auto ConfigsForInstr = generateSnippets(State, Opcode);
if (!ConfigsForInstr) {
llvm::logAllUnhandledErrors(
ConfigsForInstr.takeError(), llvm::errs(),
llvm::Twine(State.getInstrInfo().getName(Opcode)).concat(": "));
continue;
}
std::move(ConfigsForInstr->begin(), ConfigsForInstr->end(),
std::back_inserter(Configurations));
}
Configurations = ExitOnErr(generateSnippets(State, Opcode));
} else {
Configurations = ExitOnErr(readSnippets(State, SnippetsFile));
}