ninja-build / ninja

a small build system with a focus on speed
https://ninja-build.org/
Apache License 2.0
10.99k stars 1.58k forks source link

Use /proc/loadavg on Linux #2218

Open reddwarf69 opened 1 year ago

reddwarf69 commented 1 year ago

GNU Make uses /proc/loadavg on Linux to decide whether to start another task: https://lwn.net/Articles/913253/ / https://git.savannah.gnu.org/cgit/make.git/commit/src/job.c?id=d8728efc80b720c630e6b12dbd34a3d44e060690. I would like Ninja to do the same.

Since /proc/loadavg provides "number of processes currently runnable (running or on ready queue)" (https://www.kernel.org/doc/html/latest/filesystems/proc.html), Ninja would be able to take better decisions than by looking only at the info provided by getloadavg().

reddwarf69 commented 1 year ago

To have some numbers, I have a Jenkins slave with 64 cores. When pushing to a repository, multiple jobs are triggered. Those jobs are independent, and there is no easy way to have a common "jobserver" to limit the number of parallel tasks. All the makes/ninjas are run with "-j 64 -l 128".

With GNU Make 4.4 the peak 1 minute average load is 132, and the peak memory usage 55 GiB. With Ninja the peak 1 minute average load is 562, and the peak memory usage 222 GiB.

With GNU Make 4.4 it takes a 1.7% less time to complete (clean builds).

reddwarf69 commented 1 year ago

I'm not creating a PR because I'm not sure about the licence implications of me "basing" it on the GPL make patch. Maybe /proc/loadavg should be kept open the whole time instead of opening and closing in continuously, the parsing could maybe be made more robust, etc.

But this does the trick.

--- a/src/util.cc
+++ b/src/util.cc
@@ -818,6 +818,38 @@ double GetLoadAverage() {
 double GetLoadAverage() {
     return -0.0f;
 }
+#elif defined(linux)
+double GetLoadAverage() {
+  int fd = open("/proc/loadavg", O_RDONLY);
+  if(fd == -1) {
+    return -0.0f;
+  }
+
+  char avg[65];
+  int size = read(fd, avg, 64);
+  close(fd);
+  if(size == -1) {
+    return -0.0f;
+  }
+  avg[size] = '\0';
+
+  const char *p = strchr(avg, ' ');
+  if (p) {
+    p = strchr(p+1, ' ');
+  }
+  if (p) {
+    p = strchr(p+1, ' ');
+  }
+  if (!p) {
+    return -0.0f;
+  }
+
+  if((unsigned)p[1] - '0' > 9) {
+    return -0.0f;
+  }
+
+  return atoi(p+1);
+}
 #else
 double GetLoadAverage() {
   double loadavg[3] = { 0.0f, 0.0f, 0.0f };

0001-Use-proc-loadavg-on-Linux.log

I'm using it in production and my builds are now a 6.7% faster than without the patch (plus the OOM killer never kills them).

SuperSandro2000 commented 1 year ago

I'm not sure about the licence implications of me "basing" it on the GPL make patch.

There is none if you don't the copy code. If you just take the idea and reimplement it in a different program then that is fine.

haampie commented 10 months ago

Yeah, this would be pretty convenient. Given that ninja still doesn't support the GNU jobserver (#1139) , supporting /proc/loadavg would be a possibly simpler way to deal with the issue of many ninja instances spawned somewhere down the line by make -j <n>.