@@ -98,7 +98,8 @@ class InProcessFunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor {
98
98
public:
99
99
static Expected<std::unique_ptr<InProcessFunctionExecutorImpl>>
100
100
create (const LLVMState &State, object::OwningBinary<object::ObjectFile> Obj,
101
- BenchmarkRunner::ScratchSpace *Scratch) {
101
+ BenchmarkRunner::ScratchSpace *Scratch,
102
+ std::optional<int > BenchmarkProcessCPU) {
102
103
Expected<ExecutableFunction> EF =
103
104
ExecutableFunction::create (State.createTargetMachine (), std::move (Obj));
104
105
@@ -190,27 +191,31 @@ class SubProcessFunctionExecutorImpl
190
191
public:
191
192
static Expected<std::unique_ptr<SubProcessFunctionExecutorImpl>>
192
193
create (const LLVMState &State, object::OwningBinary<object::ObjectFile> Obj,
193
- const BenchmarkKey &Key) {
194
+ const BenchmarkKey &Key, std::optional< int > BenchmarkProcessCPU ) {
194
195
Expected<ExecutableFunction> EF =
195
196
ExecutableFunction::create (State.createTargetMachine (), std::move (Obj));
196
197
if (!EF)
197
198
return EF.takeError ();
198
199
199
200
return std::unique_ptr<SubProcessFunctionExecutorImpl>(
200
- new SubProcessFunctionExecutorImpl (State, std::move (*EF), Key));
201
+ new SubProcessFunctionExecutorImpl (State, std::move (*EF), Key,
202
+ BenchmarkProcessCPU));
201
203
}
202
204
203
205
private:
204
206
SubProcessFunctionExecutorImpl (const LLVMState &State,
205
207
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) {}
208
212
209
213
enum ChildProcessExitCodeE {
210
214
CounterFDReadFailed = 1 ,
211
215
RSeqDisableFailed,
212
216
FunctionDataMappingFailed,
213
- AuxiliaryMemorySetupFailed
217
+ AuxiliaryMemorySetupFailed,
218
+ SetCPUAffinityFailed
214
219
};
215
220
216
221
StringRef childProcessExitCodeToString (int ExitCode) const {
@@ -223,6 +228,8 @@ class SubProcessFunctionExecutorImpl
223
228
return " Failed to map memory for assembled snippet" ;
224
229
case ChildProcessExitCodeE::AuxiliaryMemorySetupFailed:
225
230
return " Failed to setup auxiliary memory" ;
231
+ case ChildProcessExitCodeE::SetCPUAffinityFailed:
232
+ return " Failed to set CPU affinity of the benchmarking process" ;
226
233
default :
227
234
return " Child process returned with unknown exit code" ;
228
235
}
@@ -384,6 +391,41 @@ class SubProcessFunctionExecutorImpl
384
391
return make_error<SnippetSignal>(ChildSignalInfo.si_signo );
385
392
}
386
393
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
+
387
429
Error createSubProcessAndRunBenchmark (
388
430
StringRef CounterName, SmallVectorImpl<int64_t > &CounterValues,
389
431
ArrayRef<const char *> ValidationCounters,
@@ -416,6 +458,10 @@ class SubProcessFunctionExecutorImpl
416
458
}
417
459
418
460
if (ParentOrChildPID == 0 ) {
461
+ if (BenchmarkProcessCPU.has_value ()) {
462
+ setCPUAffinityIfRequested (*BenchmarkProcessCPU);
463
+ }
464
+
419
465
// We are in the child process, close the write end of the pipe.
420
466
close (PipeFiles[1 ]);
421
467
// Unregister handlers, signal handling is now handled through ptrace in
@@ -538,6 +584,7 @@ class SubProcessFunctionExecutorImpl
538
584
const LLVMState &State;
539
585
const ExecutableFunction Function;
540
586
const BenchmarkKey &Key;
587
+ const std::optional<int > BenchmarkProcessCPU;
541
588
};
542
589
#endif // __linux__
543
590
} // namespace
@@ -615,11 +662,15 @@ BenchmarkRunner::getRunnableConfiguration(
615
662
Expected<std::unique_ptr<BenchmarkRunner::FunctionExecutor>>
616
663
BenchmarkRunner::createFunctionExecutor (
617
664
object::OwningBinary<object::ObjectFile> ObjectFile,
618
- const BenchmarkKey &Key) const {
665
+ const BenchmarkKey &Key, std::optional< int > BenchmarkProcessCPU ) const {
619
666
switch (ExecutionMode) {
620
667
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
+
621
672
auto InProcessExecutorOrErr = InProcessFunctionExecutorImpl::create (
622
- State, std::move (ObjectFile), Scratch.get ());
673
+ State, std::move (ObjectFile), Scratch.get (), BenchmarkProcessCPU );
623
674
if (!InProcessExecutorOrErr)
624
675
return InProcessExecutorOrErr.takeError ();
625
676
@@ -628,7 +679,7 @@ BenchmarkRunner::createFunctionExecutor(
628
679
case ExecutionModeE::SubProcess: {
629
680
#ifdef __linux__
630
681
auto SubProcessExecutorOrErr = SubProcessFunctionExecutorImpl::create (
631
- State, std::move (ObjectFile), Key);
682
+ State, std::move (ObjectFile), Key, BenchmarkProcessCPU );
632
683
if (!SubProcessExecutorOrErr)
633
684
return SubProcessExecutorOrErr.takeError ();
634
685
@@ -643,8 +694,8 @@ BenchmarkRunner::createFunctionExecutor(
643
694
}
644
695
645
696
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 {
648
699
Benchmark &BenchmarkResult = RC.BenchmarkResult ;
649
700
object::OwningBinary<object::ObjectFile> &ObjectFile = RC.ObjectFile ;
650
701
@@ -665,7 +716,8 @@ std::pair<Error, Benchmark> BenchmarkRunner::runConfiguration(
665
716
}
666
717
667
718
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);
669
721
if (!Executor)
670
722
return {Executor.takeError (), std::move (BenchmarkResult)};
671
723
auto NewMeasurements = runMeasurements (**Executor);
0 commit comments