Package: coreutils Version: 9.5-1 Severity: wishlist Tags: patch upstream X-Debbugs-Cc: debian-de...@lists.debian.org, debian-d...@lists.debian.org
Hi Michael, we recently considered ways of exercising more CPU cores during package builds on d-devel@l.d.o. The discussion starts at https://lists.debian.org/debian-devel/2024/11/msg00498.html. There, we considered extending debhelper and dpkg. Neither of those options looked really attractive. because they were limiting the parallelity of the complete build. However, different phases of a builds tend to require different amounts of memory. Typically linking requires more memory than compiling and test suites may have different requirements. The ninja build tool partially accommodates this by providing different "pools" for different processes limiting linker concurrency. Generally, individual packages have the best knowlegde of their individual memory requirements, but turning that into parallelism is presently inconvenient. This is where I see nproc helping. On Thu, Dec 05, 2024 at 09:23:24AM +0100, Helmut Grohne wrote: > How about instead we try to extend coreutils' nproc? How about adding > more options to it? I propose adding new options to the nproc utility to support these use cases. For one thing, I suggest adding --assume to override initial detection. This allows passing the parallel=N value from DEB_BUILD_OPTIONS as initial value to nproc. The added value arises from a second option --require-mem that reduces the amount of parallelism based on available system ram and user-provided requirements. Let me sketch some expected uses: * Typically build daemons now limit the number of system processors by downsizing VMs to avoid builds failing with OOM. Instead, they could supply an adjusted DEB_BUILD_OPTIONS=parallel=$(nproc --require-mem=2G). * Individual packages already reduce parallelism based on available memory. A number of them attempt to parse /proc/meminfo. Instead they could include /usr/share/dpkg/buildopts.mk and compute parallelism as NPROC=$(shell nproc --assume=$(or $(DEB_BUILD_OPTION_PARALLEL),1) --require-mem=3G). * When using the meson with ninja, the linker parallelism can be selected separately using backend_max_links and a different value using a different --require-mem argument can be passed. I expect these options to reduce complexity in debian/rules files and hope that in providing better tooling we can require packages to be buildable with higher default parallelism. Finding a good place to share this tooling is difficult, but nproc seems like a sensible spot. The nproc binary grows by 4kb as a result and this impacts the essential base system. I'm attaching a patch and note that a significant part of the diff is a gnulib update of the physmem module. Option naming improvable. What do you think about this approach? Helmut
--- coreutils-9.5.orig/src/nproc.c +++ coreutils-9.5/src/nproc.c @@ -23,6 +23,7 @@ #include "system.h" #include "nproc.h" +#include "physmem.h" #include "quote.h" #include "xdectoint.h" @@ -34,13 +35,17 @@ enum { ALL_OPTION = CHAR_MAX + 1, - IGNORE_OPTION + ASSUME_OPTION, + IGNORE_OPTION, + REQUIRE_RAM_OPTION }; static struct option const longopts[] = { {"all", no_argument, nullptr, ALL_OPTION}, + {"assume", required_argument, nullptr, ASSUME_OPTION}, {"ignore", required_argument, nullptr, IGNORE_OPTION}, + {"require-mem", required_argument, nullptr, REQUIRE_RAM_OPTION}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {nullptr, 0, nullptr, 0} @@ -61,7 +66,9 @@ "), stdout); fputs (_("\ --all print the number of installed processors\n\ + --assume=N assume the given number of processors before applying limits\n\ --ignore=N if possible, exclude N processing units\n\ + --require-mem=M reduce emitted processes to accommodate the given RAM per processor\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); @@ -74,7 +81,8 @@ int main (int argc, char **argv) { - unsigned long nproc, ignore = 0; + unsigned long nproc = 0, ignore = 0; + double require_ram = 0; initialize_main (&argc, &argv); set_program_name (argv[0]); setlocale (LC_ALL, ""); @@ -100,10 +108,19 @@ mode = NPROC_ALL; break; + case ASSUME_OPTION: + nproc = xdectoumax (optarg, 1, ULONG_MAX, "", _("invalid number"), 0); + break; + case IGNORE_OPTION: ignore = xdectoumax (optarg, 0, ULONG_MAX, "", _("invalid number"),0); break; + case REQUIRE_RAM_OPTION: + require_ram = xdectoumax (optarg, 1, UINTMAX_MAX, "kKmMGTPEZYRQ0", + _("invalid number"), 0); + break; + default: usage (EXIT_FAILURE); } @@ -115,13 +132,23 @@ usage (EXIT_FAILURE); } - nproc = num_processors (mode); + if (nproc == 0) + nproc = num_processors (mode); if (ignore < nproc) nproc -= ignore; else nproc = 1; + if (require_ram > 0) + { + double c = physmem_claimable(1.0) / require_ram; + if (c < 1.0) + nproc = 1; + else if (c < nproc) + nproc = c; + } + printf ("%lu\n", nproc); return EXIT_SUCCESS; --- coreutils-9.5.orig/lib/physmem.c +++ coreutils-9.5/lib/physmem.c @@ -22,25 +22,28 @@ #include "physmem.h" +#include <fcntl.h> +#include <stdio.h> #include <unistd.h> -#if HAVE_SYS_PSTAT_H +#if HAVE_SYS_PSTAT_H /* HP-UX */ # include <sys/pstat.h> #endif -#if HAVE_SYS_SYSMP_H +#if HAVE_SYS_SYSMP_H /* IRIX */ # include <sys/sysmp.h> #endif #if HAVE_SYS_SYSINFO_H +/* Linux, AIX, HP-UX, IRIX, OSF/1, Solaris, Cygwin, Android */ # include <sys/sysinfo.h> #endif -#if HAVE_MACHINE_HAL_SYSINFO_H +#if HAVE_MACHINE_HAL_SYSINFO_H /* OSF/1 */ # include <machine/hal_sysinfo.h> #endif -#if HAVE_SYS_TABLE_H +#if HAVE_SYS_TABLE_H /* OSF/1 */ # include <sys/table.h> #endif @@ -51,13 +54,16 @@ #endif #if HAVE_SYS_SYSCTL_H && !(defined __GLIBC__ && defined __linux__) +/* Linux/musl, macOS, *BSD, IRIX, Minix */ # include <sys/sysctl.h> #endif -#if HAVE_SYS_SYSTEMCFG_H +#if HAVE_SYS_SYSTEMCFG_H /* AIX */ # include <sys/systemcfg.h> #endif +#include "full-read.h" + #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN @@ -203,11 +209,84 @@ return 64 * 1024 * 1024; } -/* Return the amount of physical memory available. */ +#if defined __linux__ + +/* Get the amount of free memory and of inactive file cache memory, and + return 0. Upon failure, return -1. */ +static int +get_meminfo (unsigned long long *mem_free_p, + unsigned long long *mem_inactive_file_p) +{ + /* While the sysinfo() system call returns mem_total, mem_free, and a few + other numbers, the only way to get mem_inactive_file is by reading + /proc/meminfo. */ + int fd = open ("/proc/meminfo", O_RDONLY); + if (fd >= 0) + { + char buf[4096]; + size_t buf_size = full_read (fd, buf, sizeof (buf)); + close (fd); + if (buf_size > 0) + { + char *buf_end = buf + buf_size; + unsigned long long mem_free = 0; + unsigned long long mem_inactive_file = 0; + + /* Iterate through the lines. */ + char *line = buf; + for (;;) + { + char *p; + for (p = line; p < buf_end; p++) + if (*p == '\n') + break; + if (p == buf_end) + break; + *p = '\0'; + if (sscanf (line, "MemFree: %llu kB", &mem_free) == 1) + { + mem_free *= 1024; + } + if (sscanf (line, "Inactive(file): %llu kB", &mem_inactive_file) == 1) + { + mem_inactive_file *= 1024; + } + line = p + 1; + } + if (mem_free > 0 && mem_inactive_file > 0) + { + *mem_free_p = mem_free; + *mem_inactive_file_p = mem_inactive_file; + return 0; + } + } + } + return -1; +} + +#endif + +/* Return the amount of physical memory that can be claimed, with a given + aggressivity. */ double -physmem_available (void) +physmem_claimable (double aggressivity) { #if defined _SC_AVPHYS_PAGES && defined _SC_PAGESIZE +# if defined __linux__ + /* On Linux, sysconf (_SC_AVPHYS_PAGES) returns the amount of "free" memory. + The Linux memory management system attempts to keep only a small amount + of memory (something like 5% to 10%) as free, because memory is better + used in the file cache. + We compute the "claimable" memory as + (free memory) + aggressivity * (inactive memory in the file cache). */ + if (aggressivity > 0.0) + { + unsigned long long mem_free; + unsigned long long mem_inactive_file; + if (get_meminfo (&mem_free, &mem_inactive_file) == 0) + return (double) mem_free + aggressivity * (double) mem_inactive_file; + } +# endif { /* This works on linux-gnu, kfreebsd-gnu, solaris2, and cygwin. */ double pages = sysconf (_SC_AVPHYS_PAGES); double pagesize = sysconf (_SC_PAGESIZE); @@ -312,6 +391,12 @@ return physmem_total () / 4; } +/* Return the amount of physical memory available. */ +double +physmem_available (void) +{ + return physmem_claimable (0.0); +} #if DEBUG --- coreutils-9.5.orig/lib/physmem.h +++ coreutils-9.5/lib/physmem.h @@ -20,7 +20,35 @@ #ifndef PHYSMEM_H_ # define PHYSMEM_H_ 1 +#ifdef __cplusplus +extern "C" { +#endif + + +/* Returns the total amount of physical memory. + This value is more or less a hard limit for the working set. */ double physmem_total (void); + +/* Returns the amount of physical memory available. + This value is the amount of memory the application can use without hindering + any other process (assuming that no other process increases its memory + usage). */ double physmem_available (void); +/* Returns the amount of physical memory that can be claimed, with a given + aggressivity. + For AGGRESSIVITY == 0.0, the result is like physmem_available (): the amount + of memory the application can use without hindering any other process. + For AGGRESSIVITY == 1.0, the result is the amount of memory the application + can use, while causing memory shortage to other processes, but without + bringing the machine into an out-of-memory state. + Values in between, for example AGGRESSIVITY == 0.5, are a reasonable middle + ground. */ +double physmem_claimable (double aggressivity); + + +#ifdef __cplusplus +} +#endif + #endif /* PHYSMEM_H_ */ --- coreutils-9.5.orig/m4/physmem.m4 +++ coreutils-9.5/m4/physmem.m4 @@ -1,4 +1,5 @@ -# physmem.m4 serial 12 +# physmem.m4 +# serial 12 dnl Copyright (C) 2002-2003, 2005-2006, 2008-2024 Free Software Foundation, dnl Inc. dnl This file is free software; the Free Software Foundation