1 /* Detect the number of processors.
2
3 Copyright (C) 2009-2021 Free Software Foundation, Inc.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program; if not, see <https://www.gnu.org/licenses/>. */
17
18 /* Written by Glen Lenker and Bruno Haible. */
19
20 #include <config.h>
21 #include "nproc.h"
22
23 #include <limits.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26
27 #if HAVE_PTHREAD_GETAFFINITY_NP && 0
28 # include <pthread.h>
29 # include <sched.h>
30 #endif
31 #if HAVE_SCHED_GETAFFINITY_LIKE_GLIBC || HAVE_SCHED_GETAFFINITY_NP
32 # include <sched.h>
33 #endif
34
35 #include <sys/types.h>
36
37 #if HAVE_SYS_PSTAT_H
38 # include <sys/pstat.h>
39 #endif
40
41 #if HAVE_SYS_SYSMP_H
42 # include <sys/sysmp.h>
43 #endif
44
45 #if HAVE_SYS_PARAM_H
46 # include <sys/param.h>
47 #endif
48
49 #if HAVE_SYS_SYSCTL_H && ! defined __GLIBC__
50 # include <sys/sysctl.h>
51 #endif
52
53 #if defined _WIN32 && ! defined __CYGWIN__
54 # define WIN32_LEAN_AND_MEAN
55 # include <windows.h>
56 #endif
57
58 #include "c-ctype.h"
59
60 #include "minmax.h"
61
62 #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
63
64 /* Return the number of processors available to the current process, based
65 on a modern system call that returns the "affinity" between the current
66 process and each CPU. Return 0 if unknown or if such a system call does
67 not exist. */
68 static unsigned long
num_processors_via_affinity_mask(void)69 num_processors_via_affinity_mask (void)
70 {
71 /* glibc >= 2.3.3 with NPTL and NetBSD 5 have pthread_getaffinity_np,
72 but with different APIs. Also it requires linking with -lpthread.
73 Therefore this code is not enabled.
74 glibc >= 2.3.4 has sched_getaffinity whereas NetBSD 5 has
75 sched_getaffinity_np. */
76 #if HAVE_PTHREAD_GETAFFINITY_NP && defined __GLIBC__ && 0
77 {
78 cpu_set_t set;
79
80 if (pthread_getaffinity_np (pthread_self (), sizeof (set), &set) == 0)
81 {
82 unsigned long count;
83
84 # ifdef CPU_COUNT
85 /* glibc >= 2.6 has the CPU_COUNT macro. */
86 count = CPU_COUNT (&set);
87 # else
88 size_t i;
89
90 count = 0;
91 for (i = 0; i < CPU_SETSIZE; i++)
92 if (CPU_ISSET (i, &set))
93 count++;
94 # endif
95 if (count > 0)
96 return count;
97 }
98 }
99 #elif HAVE_PTHREAD_GETAFFINITY_NP && defined __NetBSD__ && 0
100 {
101 cpuset_t *set;
102
103 set = cpuset_create ();
104 if (set != NULL)
105 {
106 unsigned long count = 0;
107
108 if (pthread_getaffinity_np (pthread_self (), cpuset_size (set), set)
109 == 0)
110 {
111 cpuid_t i;
112
113 for (i = 0;; i++)
114 {
115 int ret = cpuset_isset (i, set);
116 if (ret < 0)
117 break;
118 if (ret > 0)
119 count++;
120 }
121 }
122 cpuset_destroy (set);
123 if (count > 0)
124 return count;
125 }
126 }
127 #elif HAVE_SCHED_GETAFFINITY_LIKE_GLIBC /* glibc >= 2.3.4 */
128 {
129 cpu_set_t set;
130
131 if (sched_getaffinity (0, sizeof (set), &set) == 0)
132 {
133 unsigned long count;
134
135 # ifdef CPU_COUNT
136 /* glibc >= 2.6 has the CPU_COUNT macro. */
137 count = CPU_COUNT (&set);
138 # else
139 size_t i;
140
141 count = 0;
142 for (i = 0; i < CPU_SETSIZE; i++)
143 if (CPU_ISSET (i, &set))
144 count++;
145 # endif
146 if (count > 0)
147 return count;
148 }
149 }
150 #elif HAVE_SCHED_GETAFFINITY_NP /* NetBSD >= 5 */
151 {
152 cpuset_t *set;
153
154 set = cpuset_create ();
155 if (set != NULL)
156 {
157 unsigned long count = 0;
158
159 if (sched_getaffinity_np (getpid (), cpuset_size (set), set) == 0)
160 {
161 cpuid_t i;
162
163 for (i = 0;; i++)
164 {
165 int ret = cpuset_isset (i, set);
166 if (ret < 0)
167 break;
168 if (ret > 0)
169 count++;
170 }
171 }
172 cpuset_destroy (set);
173 if (count > 0)
174 return count;
175 }
176 }
177 #endif
178
179 #if defined _WIN32 && ! defined __CYGWIN__
180 { /* This works on native Windows platforms. */
181 DWORD_PTR process_mask;
182 DWORD_PTR system_mask;
183
184 if (GetProcessAffinityMask (GetCurrentProcess (),
185 &process_mask, &system_mask))
186 {
187 DWORD_PTR mask = process_mask;
188 unsigned long count = 0;
189
190 for (; mask != 0; mask = mask >> 1)
191 if (mask & 1)
192 count++;
193 if (count > 0)
194 return count;
195 }
196 }
197 #endif
198
199 return 0;
200 }
201
202
203 /* Return the total number of processors. Here QUERY must be one of
204 NPROC_ALL, NPROC_CURRENT. The result is guaranteed to be at least 1. */
205 static unsigned long int
num_processors_ignoring_omp(enum nproc_query query)206 num_processors_ignoring_omp (enum nproc_query query)
207 {
208 /* On systems with a modern affinity mask system call, we have
209 sysconf (_SC_NPROCESSORS_CONF)
210 >= sysconf (_SC_NPROCESSORS_ONLN)
211 >= num_processors_via_affinity_mask ()
212 The first number is the number of CPUs configured in the system.
213 The second number is the number of CPUs available to the scheduler.
214 The third number is the number of CPUs available to the current process.
215
216 Note! On Linux systems with glibc, the first and second number come from
217 the /sys and /proc file systems (see
218 glibc/sysdeps/unix/sysv/linux/getsysstats.c).
219 In some situations these file systems are not mounted, and the sysconf call
220 returns 1 or 2 (<https://sourceware.org/bugzilla/show_bug.cgi?id=21542>),
221 which does not reflect the reality. */
222
223 if (query == NPROC_CURRENT)
224 {
225 /* Try the modern affinity mask system call. */
226 {
227 unsigned long nprocs = num_processors_via_affinity_mask ();
228
229 if (nprocs > 0)
230 return nprocs;
231 }
232
233 #if defined _SC_NPROCESSORS_ONLN
234 { /* This works on glibc, Mac OS X 10.5, FreeBSD, AIX, OSF/1, Solaris,
235 Cygwin, Haiku. */
236 long int nprocs = sysconf (_SC_NPROCESSORS_ONLN);
237 if (nprocs > 0)
238 return nprocs;
239 }
240 #endif
241 }
242 else /* query == NPROC_ALL */
243 {
244 #if defined _SC_NPROCESSORS_CONF
245 { /* This works on glibc, Mac OS X 10.5, FreeBSD, AIX, OSF/1, Solaris,
246 Cygwin, Haiku. */
247 long int nprocs = sysconf (_SC_NPROCESSORS_CONF);
248
249 # if __GLIBC__ >= 2 && defined __linux__
250 /* On Linux systems with glibc, this information comes from the /sys and
251 /proc file systems (see glibc/sysdeps/unix/sysv/linux/getsysstats.c).
252 In some situations these file systems are not mounted, and the
253 sysconf call returns 1 or 2. But we wish to guarantee that
254 num_processors (NPROC_ALL) >= num_processors (NPROC_CURRENT). */
255 if (nprocs == 1 || nprocs == 2)
256 {
257 unsigned long nprocs_current = num_processors_via_affinity_mask ();
258
259 if (/* nprocs_current > 0 && */ nprocs_current > nprocs)
260 nprocs = nprocs_current;
261 }
262 # endif
263
264 if (nprocs > 0)
265 return nprocs;
266 }
267 #endif
268 }
269
270 #if HAVE_PSTAT_GETDYNAMIC
271 { /* This works on HP-UX. */
272 struct pst_dynamic psd;
273 if (pstat_getdynamic (&psd, sizeof psd, 1, 0) >= 0)
274 {
275 /* The field psd_proc_cnt contains the number of active processors.
276 In newer releases of HP-UX 11, the field psd_max_proc_cnt includes
277 deactivated processors. */
278 if (query == NPROC_CURRENT)
279 {
280 if (psd.psd_proc_cnt > 0)
281 return psd.psd_proc_cnt;
282 }
283 else
284 {
285 if (psd.psd_max_proc_cnt > 0)
286 return psd.psd_max_proc_cnt;
287 }
288 }
289 }
290 #endif
291
292 #if HAVE_SYSMP && defined MP_NAPROCS && defined MP_NPROCS
293 { /* This works on IRIX. */
294 /* MP_NPROCS yields the number of installed processors.
295 MP_NAPROCS yields the number of processors available to unprivileged
296 processes. */
297 int nprocs =
298 sysmp (query == NPROC_CURRENT && getuid () != 0
299 ? MP_NAPROCS
300 : MP_NPROCS);
301 if (nprocs > 0)
302 return nprocs;
303 }
304 #endif
305
306 /* Finally, as fallback, use the APIs that don't distinguish between
307 NPROC_CURRENT and NPROC_ALL. */
308
309 #if HAVE_SYSCTL && ! defined __GLIBC__ && defined HW_NCPU
310 { /* This works on Mac OS X, FreeBSD, NetBSD, OpenBSD. */
311 int nprocs;
312 size_t len = sizeof (nprocs);
313 static int mib[2] = { CTL_HW, HW_NCPU };
314
315 if (sysctl (mib, ARRAY_SIZE (mib), &nprocs, &len, NULL, 0) == 0
316 && len == sizeof (nprocs)
317 && 0 < nprocs)
318 return nprocs;
319 }
320 #endif
321
322 #if defined _WIN32 && ! defined __CYGWIN__
323 { /* This works on native Windows platforms. */
324 SYSTEM_INFO system_info;
325 GetSystemInfo (&system_info);
326 if (0 < system_info.dwNumberOfProcessors)
327 return system_info.dwNumberOfProcessors;
328 }
329 #endif
330
331 return 1;
332 }
333
334 /* Parse OMP environment variables without dependence on OMP.
335 Return 0 for invalid values. */
336 static unsigned long int
parse_omp_threads(char const * threads)337 parse_omp_threads (char const* threads)
338 {
339 unsigned long int ret = 0;
340
341 if (threads == NULL)
342 return ret;
343
344 /* The OpenMP spec says that the value assigned to the environment variables
345 "may have leading and trailing white space". */
346 while (*threads != '\0' && c_isspace (*threads))
347 threads++;
348
349 /* Convert it from positive decimal to 'unsigned long'. */
350 if (c_isdigit (*threads))
351 {
352 char *endptr = NULL;
353 unsigned long int value = strtoul (threads, &endptr, 10);
354
355 if (endptr != NULL)
356 {
357 while (*endptr != '\0' && c_isspace (*endptr))
358 endptr++;
359 if (*endptr == '\0')
360 return value;
361 /* Also accept the first value in a nesting level,
362 since we can't determine the nesting level from env vars. */
363 else if (*endptr == ',')
364 return value;
365 }
366 }
367
368 return ret;
369 }
370
371 unsigned long int
num_processors(enum nproc_query query)372 num_processors (enum nproc_query query)
373 {
374 unsigned long int omp_env_limit = ULONG_MAX;
375
376 if (query == NPROC_CURRENT_OVERRIDABLE)
377 {
378 unsigned long int omp_env_threads;
379 /* Honor the OpenMP environment variables, recognized also by all
380 programs that are based on OpenMP. */
381 omp_env_threads = parse_omp_threads (getenv ("OMP_NUM_THREADS"));
382 omp_env_limit = parse_omp_threads (getenv ("OMP_THREAD_LIMIT"));
383 if (! omp_env_limit)
384 omp_env_limit = ULONG_MAX;
385
386 if (omp_env_threads)
387 return MIN (omp_env_threads, omp_env_limit);
388
389 query = NPROC_CURRENT;
390 }
391 /* Here query is one of NPROC_ALL, NPROC_CURRENT. */
392 {
393 unsigned long nprocs = num_processors_ignoring_omp (query);
394 return MIN (nprocs, omp_env_limit);
395 }
396 }
397