Skip to content

Commit e093bb9

Browse files
Reland "[llvm-exegesis] Add support for pinning benchmarking process to a CPU (#85168)" (#109688)
This reverts commit 2cd20c2. This relands commit 9886788. This was causing more buildbot failures due to getcpu not being available with glibc <=2.29. This patch fixes that by directly making the syscall, assuming the syscall number macro is available.
1 parent f76dae1 commit e093bb9

File tree

5 files changed

+88
-15
lines changed

5 files changed

+88
-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: 64 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,41 @@ class SubProcessFunctionExecutorImpl
384391
return make_error<SnippetSignal>(ChildSignalInfo.si_signo);
385392
}
386393

394+
static void setCPUAffinityIfRequested(int CPUToUse) {
395+
// Special case this function for x86_64 for now as certain more esoteric
396+
// platforms have different definitions for some of the libc functions that
397+
// cause buildtime failures. Additionally, the subprocess executor mode (the
398+
// sole mode where this is supported) currently only supports x86_64.
399+
400+
// Also check that we have the SYS_getcpu macro defined, meaning the syscall
401+
// actually exists within the build environment. We manually use the syscall
402+
// rather than the libc wrapper given the wrapper for getcpu is only available
403+
// in glibc 2.29 and later.
404+
#if defined(__x86_64__) && defined(SYS_getcpu)
405+
// Set the CPU affinity for the child process, so that we ensure that if
406+
// the user specified a CPU the process should run on, the benchmarking
407+
// process is running on that CPU.
408+
cpu_set_t CPUMask;
409+
CPU_ZERO(&CPUMask);
410+
CPU_SET(CPUToUse, &CPUMask);
411+
// TODO(boomanaiden154): Rewrite this to use LLVM primitives once they
412+
// are available.
413+
int SetAffinityReturn = sched_setaffinity(0, sizeof(CPUMask), &CPUMask);
414+
if (SetAffinityReturn == -1) {
415+
exit(ChildProcessExitCodeE::SetCPUAffinityFailed);
416+
}
417+
418+
// Check (if assertions are enabled) that we are actually running on the
419+
// CPU that was specified by the user.
420+
[[maybe_unused]] unsigned int CurrentCPU;
421+
assert(syscall(SYS_getcpu, &CurrentCPU, nullptr) == 0 &&
422+
"Expected getcpu call to succeed.");
423+
assert(static_cast<int>(CurrentCPU) == CPUToUse &&
424+
"Expected current CPU to equal the CPU requested by the user");
425+
#endif // defined(__x86_64__) && defined(SYS_getcpu)
426+
exit(ChildProcessExitCodeE::SetCPUAffinityFailed);
427+
}
428+
387429
Error createSubProcessAndRunBenchmark(
388430
StringRef CounterName, SmallVectorImpl<int64_t> &CounterValues,
389431
ArrayRef<const char *> ValidationCounters,
@@ -416,6 +458,10 @@ class SubProcessFunctionExecutorImpl
416458
}
417459

418460
if (ParentOrChildPID == 0) {
461+
if (BenchmarkProcessCPU.has_value()) {
462+
setCPUAffinityIfRequested(*BenchmarkProcessCPU);
463+
}
464+
419465
// We are in the child process, close the write end of the pipe.
420466
close(PipeFiles[1]);
421467
// Unregister handlers, signal handling is now handled through ptrace in
@@ -538,6 +584,7 @@ class SubProcessFunctionExecutorImpl
538584
const LLVMState &State;
539585
const ExecutableFunction Function;
540586
const BenchmarkKey &Key;
587+
const std::optional<int> BenchmarkProcessCPU;
541588
};
542589
#endif // __linux__
543590
} // namespace
@@ -615,11 +662,15 @@ BenchmarkRunner::getRunnableConfiguration(
615662
Expected<std::unique_ptr<BenchmarkRunner::FunctionExecutor>>
616663
BenchmarkRunner::createFunctionExecutor(
617664
object::OwningBinary<object::ObjectFile> ObjectFile,
618-
const BenchmarkKey &Key) const {
665+
const BenchmarkKey &Key, std::optional<int> BenchmarkProcessCPU) const {
619666
switch (ExecutionMode) {
620667
case ExecutionModeE::InProcess: {
668+
if (BenchmarkProcessCPU.has_value())
669+
return make_error<Failure>("The inprocess execution mode does not "
670+
"support benchmark core pinning.");
671+
621672
auto InProcessExecutorOrErr = InProcessFunctionExecutorImpl::create(
622-
State, std::move(ObjectFile), Scratch.get());
673+
State, std::move(ObjectFile), Scratch.get(), BenchmarkProcessCPU);
623674
if (!InProcessExecutorOrErr)
624675
return InProcessExecutorOrErr.takeError();
625676

@@ -628,7 +679,7 @@ BenchmarkRunner::createFunctionExecutor(
628679
case ExecutionModeE::SubProcess: {
629680
#ifdef __linux__
630681
auto SubProcessExecutorOrErr = SubProcessFunctionExecutorImpl::create(
631-
State, std::move(ObjectFile), Key);
682+
State, std::move(ObjectFile), Key, BenchmarkProcessCPU);
632683
if (!SubProcessExecutorOrErr)
633684
return SubProcessExecutorOrErr.takeError();
634685

@@ -643,8 +694,8 @@ BenchmarkRunner::createFunctionExecutor(
643694
}
644695

645696
std::pair<Error, Benchmark> BenchmarkRunner::runConfiguration(
646-
RunnableConfiguration &&RC,
647-
const std::optional<StringRef> &DumpFile) const {
697+
RunnableConfiguration &&RC, const std::optional<StringRef> &DumpFile,
698+
std::optional<int> BenchmarkProcessCPU) const {
648699
Benchmark &BenchmarkResult = RC.BenchmarkResult;
649700
object::OwningBinary<object::ObjectFile> &ObjectFile = RC.ObjectFile;
650701

@@ -665,7 +716,8 @@ std::pair<Error, Benchmark> BenchmarkRunner::runConfiguration(
665716
}
666717

667718
Expected<std::unique_ptr<BenchmarkRunner::FunctionExecutor>> Executor =
668-
createFunctionExecutor(std::move(ObjectFile), RC.BenchmarkResult.Key);
719+
createFunctionExecutor(std::move(ObjectFile), RC.BenchmarkResult.Key,
720+
BenchmarkProcessCPU);
669721
if (!Executor)
670722
return {Executor.takeError(), std::move(BenchmarkResult)};
671723
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)