Skip to content

Commit 9886788

Browse files
[llvm-exegesis] Add support for pinning benchmarking process to a CPU (llvm#85168)
This patch adds in support for pinning a benchmarking process to a specific CPU (in the subprocess benchmarking mode on Linux). This is intended to be used in environments where a certain set of CPUs is isolated from the scheduler using something like cgroups and thus should present less potential for noise than normal. This also opens up the door for doing multithreaded benchmarking as we can now pin benchmarking processes to specific CPUs that we know won't interfere with each other.
1 parent aa5eff9 commit 9886788

File tree

5 files changed

+76
-15
lines changed

5 files changed

+76
-15
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# REQUIRES: exegesis-can-measure-latency, x86_64-linux
2+
3+
# RUN: not llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -opcode-name=ADD64rr -execution-mode=inprocess --benchmark-process-cpu=0 2>&1 | FileCheck %s
4+
5+
# CHECK: llvm-exegesis error: The inprocess execution mode does not support benchmark core pinning.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# REQUIRES: exegesis-can-measure-latency, x86_64-linux
2+
3+
# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -opcode-name=ADD64rr -execution-mode=subprocess | FileCheck %s
4+
5+
# CHECK: - { key: latency, value: {{[0-9.]*}}, per_snippet_value: {{[0-9.]*}}

llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ class InProcessFunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor {
9898
public:
9999
static Expected<std::unique_ptr<InProcessFunctionExecutorImpl>>
100100
create(const LLVMState &State, object::OwningBinary<object::ObjectFile> Obj,
101-
BenchmarkRunner::ScratchSpace *Scratch) {
101+
BenchmarkRunner::ScratchSpace *Scratch,
102+
std::optional<int> BenchmarkProcessCPU) {
102103
Expected<ExecutableFunction> EF =
103104
ExecutableFunction::create(State.createTargetMachine(), std::move(Obj));
104105

@@ -190,27 +191,31 @@ class SubProcessFunctionExecutorImpl
190191
public:
191192
static Expected<std::unique_ptr<SubProcessFunctionExecutorImpl>>
192193
create(const LLVMState &State, object::OwningBinary<object::ObjectFile> Obj,
193-
const BenchmarkKey &Key) {
194+
const BenchmarkKey &Key, std::optional<int> BenchmarkProcessCPU) {
194195
Expected<ExecutableFunction> EF =
195196
ExecutableFunction::create(State.createTargetMachine(), std::move(Obj));
196197
if (!EF)
197198
return EF.takeError();
198199

199200
return std::unique_ptr<SubProcessFunctionExecutorImpl>(
200-
new SubProcessFunctionExecutorImpl(State, std::move(*EF), Key));
201+
new SubProcessFunctionExecutorImpl(State, std::move(*EF), Key,
202+
BenchmarkProcessCPU));
201203
}
202204

203205
private:
204206
SubProcessFunctionExecutorImpl(const LLVMState &State,
205207
ExecutableFunction Function,
206-
const BenchmarkKey &Key)
207-
: State(State), Function(std::move(Function)), Key(Key) {}
208+
const BenchmarkKey &Key,
209+
std::optional<int> BenchmarkCPU)
210+
: State(State), Function(std::move(Function)), Key(Key),
211+
BenchmarkProcessCPU(BenchmarkCPU) {}
208212

209213
enum ChildProcessExitCodeE {
210214
CounterFDReadFailed = 1,
211215
RSeqDisableFailed,
212216
FunctionDataMappingFailed,
213-
AuxiliaryMemorySetupFailed
217+
AuxiliaryMemorySetupFailed,
218+
SetCPUAffinityFailed
214219
};
215220

216221
StringRef childProcessExitCodeToString(int ExitCode) const {
@@ -223,6 +228,8 @@ class SubProcessFunctionExecutorImpl
223228
return "Failed to map memory for assembled snippet";
224229
case ChildProcessExitCodeE::AuxiliaryMemorySetupFailed:
225230
return "Failed to setup auxiliary memory";
231+
case ChildProcessExitCodeE::SetCPUAffinityFailed:
232+
return "Failed to set CPU affinity of the benchmarking process";
226233
default:
227234
return "Child process returned with unknown exit code";
228235
}
@@ -384,6 +391,29 @@ class SubProcessFunctionExecutorImpl
384391
return make_error<SnippetSignal>(ChildSignalInfo.si_signo);
385392
}
386393

394+
static void setCPUAffinityIfRequested(int CPUToUse) {
395+
// Set the CPU affinity for the child process, so that we ensure that if
396+
// the user specified a CPU the process should run on, the benchmarking
397+
// process is running on that CPU.
398+
cpu_set_t CPUMask;
399+
CPU_ZERO(&CPUMask);
400+
CPU_SET(CPUToUse, &CPUMask);
401+
// TODO(boomanaiden154): Rewrite this to use LLVM primitives once they
402+
// are available.
403+
int SetAffinityReturn = sched_setaffinity(0, sizeof(CPUMask), &CPUMask);
404+
if (SetAffinityReturn == -1) {
405+
exit(ChildProcessExitCodeE::SetCPUAffinityFailed);
406+
}
407+
408+
// Check (if assertions are enabled) that we are actually running on the
409+
// CPU that was specified by the user.
410+
unsigned int CurrentCPU;
411+
assert(getcpu(&CurrentCPU, nullptr) == 0 &&
412+
"Expected getcpu call to succeed.");
413+
assert(static_cast<int>(CurrentCPU) == CPUToUse &&
414+
"Expected current CPU to equal the CPU requested by the user");
415+
}
416+
387417
Error createSubProcessAndRunBenchmark(
388418
StringRef CounterName, SmallVectorImpl<int64_t> &CounterValues,
389419
ArrayRef<const char *> ValidationCounters,
@@ -416,6 +446,10 @@ class SubProcessFunctionExecutorImpl
416446
}
417447

418448
if (ParentOrChildPID == 0) {
449+
if (BenchmarkProcessCPU.has_value()) {
450+
setCPUAffinityIfRequested(*BenchmarkProcessCPU);
451+
}
452+
419453
// We are in the child process, close the write end of the pipe.
420454
close(PipeFiles[1]);
421455
// Unregister handlers, signal handling is now handled through ptrace in
@@ -538,6 +572,7 @@ class SubProcessFunctionExecutorImpl
538572
const LLVMState &State;
539573
const ExecutableFunction Function;
540574
const BenchmarkKey &Key;
575+
const std::optional<int> BenchmarkProcessCPU;
541576
};
542577
#endif // __linux__
543578
} // namespace
@@ -615,11 +650,15 @@ BenchmarkRunner::getRunnableConfiguration(
615650
Expected<std::unique_ptr<BenchmarkRunner::FunctionExecutor>>
616651
BenchmarkRunner::createFunctionExecutor(
617652
object::OwningBinary<object::ObjectFile> ObjectFile,
618-
const BenchmarkKey &Key) const {
653+
const BenchmarkKey &Key, std::optional<int> BenchmarkProcessCPU) const {
619654
switch (ExecutionMode) {
620655
case ExecutionModeE::InProcess: {
656+
if (BenchmarkProcessCPU.has_value())
657+
return make_error<Failure>("The inprocess execution mode does not "
658+
"support benchmark core pinning.");
659+
621660
auto InProcessExecutorOrErr = InProcessFunctionExecutorImpl::create(
622-
State, std::move(ObjectFile), Scratch.get());
661+
State, std::move(ObjectFile), Scratch.get(), BenchmarkProcessCPU);
623662
if (!InProcessExecutorOrErr)
624663
return InProcessExecutorOrErr.takeError();
625664

@@ -628,7 +667,7 @@ BenchmarkRunner::createFunctionExecutor(
628667
case ExecutionModeE::SubProcess: {
629668
#ifdef __linux__
630669
auto SubProcessExecutorOrErr = SubProcessFunctionExecutorImpl::create(
631-
State, std::move(ObjectFile), Key);
670+
State, std::move(ObjectFile), Key, BenchmarkProcessCPU);
632671
if (!SubProcessExecutorOrErr)
633672
return SubProcessExecutorOrErr.takeError();
634673

@@ -643,8 +682,8 @@ BenchmarkRunner::createFunctionExecutor(
643682
}
644683

645684
std::pair<Error, Benchmark> BenchmarkRunner::runConfiguration(
646-
RunnableConfiguration &&RC,
647-
const std::optional<StringRef> &DumpFile) const {
685+
RunnableConfiguration &&RC, const std::optional<StringRef> &DumpFile,
686+
std::optional<int> BenchmarkProcessCPU) const {
648687
Benchmark &BenchmarkResult = RC.BenchmarkResult;
649688
object::OwningBinary<object::ObjectFile> &ObjectFile = RC.ObjectFile;
650689

@@ -665,7 +704,8 @@ std::pair<Error, Benchmark> BenchmarkRunner::runConfiguration(
665704
}
666705

667706
Expected<std::unique_ptr<BenchmarkRunner::FunctionExecutor>> Executor =
668-
createFunctionExecutor(std::move(ObjectFile), RC.BenchmarkResult.Key);
707+
createFunctionExecutor(std::move(ObjectFile), RC.BenchmarkResult.Key,
708+
BenchmarkProcessCPU);
669709
if (!Executor)
670710
return {Executor.takeError(), std::move(BenchmarkResult)};
671711
auto NewMeasurements = runMeasurements(**Executor);

llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ class BenchmarkRunner {
6868

6969
std::pair<Error, Benchmark>
7070
runConfiguration(RunnableConfiguration &&RC,
71-
const std::optional<StringRef> &DumpFile) const;
71+
const std::optional<StringRef> &DumpFile,
72+
std::optional<int> BenchmarkProcessCPU) const;
7273

7374
// Scratch space to run instructions that touch memory.
7475
struct ScratchSpace {
@@ -135,7 +136,8 @@ class BenchmarkRunner {
135136

136137
Expected<std::unique_ptr<FunctionExecutor>>
137138
createFunctionExecutor(object::OwningBinary<object::ObjectFile> Obj,
138-
const BenchmarkKey &Key) const;
139+
const BenchmarkKey &Key,
140+
std::optional<int> BenchmarkProcessCPU) const;
139141
};
140142

141143
} // namespace exegesis

llvm/tools/llvm-exegesis/llvm-exegesis.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,11 @@ static cl::list<ValidationEvent> ValidationCounters(
269269
"counter to validate benchmarking assumptions"),
270270
cl::CommaSeparated, cl::cat(BenchmarkOptions), ValidationEventOptions());
271271

272+
static cl::opt<int> BenchmarkProcessCPU(
273+
"benchmark-process-cpu",
274+
cl::desc("The CPU number that the benchmarking process should executon on"),
275+
cl::cat(BenchmarkOptions), cl::init(-1));
276+
272277
static ExitOnError ExitOnErr("llvm-exegesis error: ");
273278

274279
// Helper function that logs the error(s) and exits.
@@ -418,8 +423,12 @@ static void runBenchmarkConfigurations(
418423
std::optional<StringRef> DumpFile;
419424
if (DumpObjectToDisk.getNumOccurrences())
420425
DumpFile = DumpObjectToDisk;
426+
const std::optional<int> BenchmarkCPU =
427+
BenchmarkProcessCPU == -1
428+
? std::nullopt
429+
: std::optional(BenchmarkProcessCPU.getValue());
421430
auto [Err, BenchmarkResult] =
422-
Runner.runConfiguration(std::move(RC), DumpFile);
431+
Runner.runConfiguration(std::move(RC), DumpFile, BenchmarkCPU);
423432
if (Err) {
424433
// Errors from executing the snippets are fine.
425434
// All other errors are a framework issue and should fail.

0 commit comments

Comments
 (0)