|
12 | 12 | #include <fstream> |
13 | 13 | #include <pthread.h> |
14 | 14 | #include <sched.h> |
| 15 | +#include <set> |
15 | 16 | #endif |
16 | 17 |
|
17 | 18 | namespace stellar |
18 | 19 | { |
19 | 20 |
|
20 | 21 | namespace |
21 | 22 | { |
22 | | -// Returns one logical CPU id per physical core: every CPU with the smallest id |
23 | | -// among its thread-siblings. Empty on failure or on non-Linux platforms. |
| 23 | +// Returns one logical CPU id per physical core, restricted to the CPUs this |
| 24 | +// process is actually allowed to run on. Empty on failure or on non-Linux |
| 25 | +// platforms. |
| 26 | +// |
| 27 | +// The set of usable CPUs is discovered from the thread's affinity mask, not |
| 28 | +// assumed to be the contiguous range 0..hardware_concurrency()-1. |
| 29 | +// hardware_concurrency() is only a count, and the allowed set is commonly |
| 30 | +// sparse or non-zero-based (cgroup/cpuset confinement, taskset, or offlined |
| 31 | +// CPUs). Iterating by count would read topology for, and pin to, CPUs the |
| 32 | +// process cannot use - pthread_setaffinity_np then fails with EINVAL on every |
| 33 | +// worker even though usable physical cores exist. |
24 | 34 | std::vector<unsigned> |
25 | 35 | physicalCoreRepresentatives() |
26 | 36 | { |
27 | 37 | std::vector<unsigned> res; |
28 | 38 | #ifdef __linux__ |
29 | | - unsigned n = std::thread::hardware_concurrency(); |
30 | | - for (unsigned cpu = 0; cpu < n; ++cpu) |
| 39 | + cpu_set_t affinity; |
| 40 | + CPU_ZERO(&affinity); |
| 41 | + if (sched_getaffinity(0, sizeof(affinity), &affinity) != 0) |
31 | 42 | { |
| 43 | + // Affinity unavailable (e.g. more than CPU_SETSIZE logical CPUs); skip |
| 44 | + // pinning rather than guess. |
| 45 | + return {}; |
| 46 | + } |
| 47 | + |
| 48 | + // Walk the allowed CPUs in ascending order and keep the first (hence |
| 49 | + // smallest) allowed sibling of each physical core. thread_siblings_list |
| 50 | + // begins with the smallest sibling id, which is a stable per-core |
| 51 | + // identifier independent of the affinity mask, so it lets us collapse the |
| 52 | + // logical CPUs of one core down to a single representative. |
| 53 | + std::unordered_set<unsigned> seenCores; |
| 54 | + for (unsigned cpu = 0; cpu < CPU_SETSIZE; ++cpu) |
| 55 | + { |
| 56 | + if (!CPU_ISSET(cpu, &affinity)) |
| 57 | + { |
| 58 | + continue; |
| 59 | + } |
32 | 60 | std::ifstream in("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + |
33 | 61 | "/topology/thread_siblings_list"); |
34 | 62 | // The list is formatted like "0,16" or "0-1"; the first (smallest) |
35 | | - // entry is the leading integer either way. |
36 | | - unsigned firstSibling; |
37 | | - if (!(in >> firstSibling)) |
| 63 | + // entry is the leading integer either way and identifies the core. |
| 64 | + unsigned coreId; |
| 65 | + if (!(in >> coreId)) |
38 | 66 | { |
| 67 | + // Topology is unreadable for a CPU we are allowed to use, so skip |
| 68 | + // pinning rather than guess. |
39 | 69 | return {}; |
40 | 70 | } |
41 | | - if (firstSibling == cpu) |
| 71 | + if (seenCores.insert(coreId).second) |
42 | 72 | { |
43 | 73 | res.push_back(cpu); |
44 | 74 | } |
|
0 commit comments