Skip to content

Commit add119c

Browse files
Match concurrency to available CPU bandwidth
This change allows ninja to throttle number of parallel tasks based on feedback from cpuacct cgroup controller. It extends "-l" parameter to accept negative values; "-l-NN" means that ninja should limit concurrency when processes in current cgroup spend more than NN% of their time waiting for CPU slice. E.g., running "ninja -j100 -l-10" on 32-core machine will quickly settle on parallelism of 32-34. This option is designed to make ninja use all CPU bandwidth available to a cgroup-based container, while not starting excessive number of processes, which could eat up all RAM.
1 parent e72d1d5 commit add119c

File tree

3 files changed

+89
-4
lines changed

3 files changed

+89
-4
lines changed

src/build.cc

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -692,10 +692,19 @@ void RealCommandRunner::Abort() {
692692

693693
bool RealCommandRunner::CanRunMore() const {
694694
size_t subproc_number =
695-
subprocs_.running_.size() + subprocs_.finished_.size();
696-
return (int)subproc_number < config_.parallelism
697-
&& ((subprocs_.running_.empty() || config_.max_load_average <= 0.0f)
698-
|| GetLoadAverage() < config_.max_load_average);
695+
subprocs_.running_.size() + subprocs_.finished_.size();
696+
697+
if ((int)subproc_number >= config_.parallelism)
698+
return false;
699+
700+
if (subprocs_.running_.empty())
701+
return true;
702+
703+
if (config_.max_load_average > 0.0f)
704+
return GetLoadAverage() < config_.max_load_average;
705+
else
706+
return (GetCPUWaitRatio(subproc_number, config_.parallelism)
707+
< -config_.max_load_average);
699708
}
700709

701710
bool RealCommandRunner::StartCommand(Edge* edge) {

src/util.cc

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#elif defined(_AIX) && !defined(__PASE__)
4949
#include <libperfstat.h>
5050
#elif defined(linux) || defined(__GLIBC__)
51+
#include <fstream>
5152
#include <sys/sysinfo.h>
5253
#endif
5354

@@ -597,6 +598,77 @@ double GetLoadAverage() {
597598
}
598599
#endif // _WIN32
599600

601+
double GetCPUWaitRatio(size_t subproc_number, int parallelism) {
602+
#if defined(linux)
603+
static double oncpu_ratio = 100.0;
604+
static uint64_t prev_user(0), prev_system(0), prev_wall(0);
605+
606+
uint64_t user(0), system(0), wall(0);
607+
string token;
608+
609+
// Fetch user, system and timestamp counters.
610+
ifstream cpustat("/sys/fs/cgroup/cpuacct/cpuacct.stat", ifstream::in);
611+
while (cpustat >> token) {
612+
if (token == "user")
613+
cpustat >> user;
614+
else if (token == "system")
615+
cpustat >> system;
616+
}
617+
ifstream schedstat("/proc/schedstat", ifstream::in);
618+
while (schedstat >> token) {
619+
if (token == "timestamp") {
620+
schedstat >> wall;
621+
break;
622+
}
623+
}
624+
625+
if (user > 0 && system > 0 && wall > 0)
626+
{
627+
uint64_t oncpu_ticks = (user - prev_user) + (system - prev_system);
628+
uint64_t wall_ticks = (wall - prev_wall) * subproc_number;
629+
630+
// Adjust CONFIG_HZ vs _SC_CLK_TCK scaling.
631+
// CONFIG_HZ is set to 250 in Ubuntu kernels, and I can't find a good
632+
// way to query this setting from user-space.
633+
oncpu_ticks *= 250; // CONFIG_HZ
634+
wall_ticks *= sysconf(_SC_CLK_TCK);
635+
636+
// oncpu_ticks is the amount of time that processes in our cgroup
637+
// spent on all CPUs combined since previous measurement.
638+
// wall_ticks is the amount of time that elapsed since previous
639+
// measurement scaled by number of current subprocesses.
640+
//
641+
// Therefore, wall_ticks is the time that subprocesses should have
642+
// got on CPU with no contention, and oncpu_ticks is the time that
643+
// they have actually were allowed to run up on all CPUs.
644+
645+
if (wall_ticks > 0) {
646+
// Clock advanced, so update oncpu_ratio with latest measurments.
647+
// Pass new measurements through a simple noise filter.
648+
if (prev_user != 0) {
649+
oncpu_ratio *= ((double) subproc_number
650+
/ (subproc_number + 1));
651+
oncpu_ratio += ((100.0 * oncpu_ticks / wall_ticks)
652+
/ (subproc_number + 1));
653+
}
654+
655+
prev_user = user;
656+
prev_system = system;
657+
prev_wall = wall;
658+
} else
659+
// Clock didn't advance, this usually happens during initial
660+
// startup, when we start config_.parallelism tasks in rapid
661+
// succession. Slightly reduce oncpu_ratio to throttle startup
662+
// of new processes until we get an updated measurement.
663+
oncpu_ratio *= (double) parallelism / (parallelism + 1);
664+
}
665+
666+
return 100.0 - oncpu_ratio;
667+
#else
668+
return -1.0;
669+
#endif
670+
}
671+
600672
string ElideMiddle(const string& str, size_t width) {
601673
switch (width) {
602674
case 0: return "";

src/util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ int GetProcessorCount();
9797
/// on error.
9898
double GetLoadAverage();
9999

100+
/// @return percentage of time tasks are waiting for CPU.
101+
/// A negative value is returned for unsupported platforms.
102+
double GetCPUWaitRatio(size_t subproc_number, int parallelism);
103+
100104
/// Elide the given string @a str with '...' in the middle if the length
101105
/// exceeds @a width.
102106
std::string ElideMiddle(const std::string& str, size_t width);

0 commit comments

Comments
 (0)