@@ -98,7 +98,8 @@ class InProcessFunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor {
9898public:
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
190191public:
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
203205private:
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(
615662Expected<std::unique_ptr<BenchmarkRunner::FunctionExecutor>>
616663BenchmarkRunner::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
645696std::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);
0 commit comments