ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
cpu_topology.hpp
Go to the documentation of this file.
1#pragma once
2
14#include "../thread_affinity.hpp"
15#include <algorithm>
16#include <charconv>
17#include <cstdint>
18#include <string_view>
19#include <thread>
20#include <vector>
21
22#ifndef _WIN32
23# include <fstream>
24# include <string>
25#endif
26
27#ifdef _WIN32
28# include "../detail/windows_api.hpp"
29#endif
30
32{
33
34#ifndef _WIN32
35namespace topology_detail
36{
37[[nodiscard]] inline auto
38parse_index_list(std::string_view input) -> std::vector<int>
39{
40 std::vector<int> values;
41 auto const* cursor = input.data();
42 auto const* const end = cursor + input.size();
43
44 while (cursor != end)
45 {
46 while (cursor != end && (*cursor == ',' || *cursor == ' ' || *cursor == '\t' || *cursor == '\n'))
47 ++cursor;
48 if (cursor == end)
49 break;
50
51 int first = 0;
52 auto parsed = std::from_chars(cursor, end, first);
53 if (parsed.ec != std::errc{} || parsed.ptr == cursor || first < 0)
54 return {};
55 cursor = parsed.ptr;
56
57 int last = first;
58 if (cursor != end && *cursor == '-')
59 {
60 ++cursor;
61 parsed = std::from_chars(cursor, end, last);
62 if (parsed.ec != std::errc{} || parsed.ptr == cursor || last < first)
63 return {};
64 cursor = parsed.ptr;
65 }
66
67 for (int value = first;; ++value)
68 {
69 values.push_back(value);
70 if (value == last)
71 break;
72 }
73
74 if (cursor != end && *cursor != ',' && *cursor != ' ' && *cursor != '\t' && *cursor != '\n')
75 return {};
76 }
77
78 std::sort(values.begin(), values.end());
79 values.erase(std::unique(values.begin(), values.end()), values.end());
80 return values;
81}
82
83[[nodiscard]] inline auto
84read_index_list(std::string const& path) -> std::vector<int>
85{
86 std::ifstream input(path);
87 std::string contents;
88 if (!std::getline(input, contents))
89 return {};
90 return parse_index_list(contents);
91}
92} // namespace topology_detail
93#endif
94
108{
109 std::size_t cpu_count{ 0 };
110 std::size_t numa_nodes{ 1 };
111 std::vector<std::vector<cpu_id>> node_to_cpus;
112};
113
122inline auto
124{
125 cpu_topology topo;
126 topo.cpu_count = std::thread::hardware_concurrency();
127 if (topo.cpu_count == 0)
128 topo.cpu_count = 1;
129
130#ifdef _WIN32
131 topo.numa_nodes = 1;
132 topo.node_to_cpus = { {} };
133 topo.cpu_count = 0;
134
135# ifndef THREADSCHEDULE_WINDOWS_VISTA_COMPAT
136 using get_active_processor_group_count_fn = WORD(WINAPI*)();
137 using get_active_processor_count_fn = DWORD(WINAPI*)(WORD);
138
139 HMODULE const kernel32 = GetModuleHandleW(L"kernel32.dll");
140 if (kernel32)
141 {
142 auto const get_group_count = reinterpret_cast<get_active_processor_group_count_fn>(
143 reinterpret_cast<void*>(GetProcAddress(kernel32, "GetActiveProcessorGroupCount")));
144 auto const get_processor_count = reinterpret_cast<get_active_processor_count_fn>(
145 reinterpret_cast<void*>(GetProcAddress(kernel32, "GetActiveProcessorCount")));
146 if (get_group_count && get_processor_count)
147 {
148 WORD const group_count = get_group_count();
149 for (WORD group = 0; group < group_count; ++group)
150 {
151 DWORD const processor_count = get_processor_count(group);
152 if (processor_count == 0)
153 continue;
154 topo.cpu_count += static_cast<std::size_t>(processor_count);
155 for (DWORD index = 0; index < processor_count; ++index)
156 topo.node_to_cpus[0].emplace_back(static_cast<int>(group) * 64 + static_cast<int>(index));
157 }
158 }
159 }
160# endif
161
162 if (topo.node_to_cpus[0].empty())
163 {
164 SYSTEM_INFO system_info{};
165 GetSystemInfo(&system_info);
166 DWORD const processor_count = system_info.dwNumberOfProcessors > 0 ? system_info.dwNumberOfProcessors : 1;
167 topo.cpu_count = static_cast<std::size_t>(processor_count);
168 for (DWORD index = 0; index < processor_count; ++index)
169 topo.node_to_cpus[0].emplace_back(static_cast<int>(index));
170 }
171#else
172 auto node_ids = topology_detail::read_index_list("/sys/devices/system/node/has_cpu");
173 if (node_ids.empty())
174 node_ids = topology_detail::read_index_list("/sys/devices/system/node/online");
175
176 for (int const node_id : node_ids)
177 {
178 auto const cpu_indices
179 = topology_detail::read_index_list("/sys/devices/system/node/node" + std::to_string(node_id) + "/cpulist");
180 if (cpu_indices.empty())
181 continue;
182 std::vector<cpu_id> cpus;
183 cpus.reserve(cpu_indices.size());
184 for (int const cpu : cpu_indices)
185 cpus.emplace_back(cpu);
186 topo.node_to_cpus.push_back(std::move(cpus));
187 }
188
189 if (topo.node_to_cpus.empty())
190 {
191 topo.node_to_cpus = { {} };
192 for (std::size_t i = 0; i < topo.cpu_count; ++i)
193 topo.node_to_cpus[0].emplace_back(static_cast<std::int64_t>(i));
194 }
195 topo.numa_nodes = topo.node_to_cpus.size();
196#endif
197 return topo;
198}
199
209inline auto
210affinity_for_node(cpu_topology const& topo, int node_index, int thread_index, int threads_per_node = 1)
212{
213 auto const available_nodes = (std::min)(topo.numa_nodes, topo.node_to_cpus.size());
214 if (available_nodes == 0 || threads_per_node <= 0)
215 return {};
216 auto const wrapped_index = [](int value, std::size_t count)
217 {
218 if (value >= 0)
219 return static_cast<std::size_t>(value) % count;
220 auto const magnitude = static_cast<std::uint64_t>(-(static_cast<std::int64_t>(value) + 1)) + 1;
221 auto const remainder = static_cast<std::size_t>(magnitude % count);
222 return remainder == 0 ? std::size_t{ 0 } : count - remainder;
223 };
224 auto const n = wrapped_index(node_index, available_nodes);
225 auto const& cpus = topo.node_to_cpus[n];
226 thread_affinity aff;
227 if (cpus.empty())
228 return aff;
229
230 auto const cpu_count = cpus.size();
231 auto const first = wrapped_index(thread_index, cpu_count);
232 cpu_id const cpu = cpus[first];
233 aff.add_cpu(cpu);
234 for (int k = 1; k < threads_per_node; ++k)
235 {
236 cpu_id const extra = cpus[(first + static_cast<std::size_t>(k)) % cpu_count];
237 aff.add_cpu(extra);
238 }
239 return aff;
240}
241
251inline auto
252affinity_for_node(int node_index, int thread_index, int threads_per_node = 1) -> thread_affinity
253{
254 return affinity_for_node(read_topology(), node_index, thread_index, threads_per_node);
255}
256
266inline auto
267distribute_affinities_by_numa(cpu_topology const& topo, size_t num_threads) -> std::vector<thread_affinity>
268{
269 std::vector<thread_affinity> result;
270 result.reserve(num_threads);
271 for (size_t i = 0; i < num_threads; ++i)
272 {
273 int node = (topo.numa_nodes > 0) ? static_cast<int>(i % topo.numa_nodes) : 0;
274 result.push_back(affinity_for_node(topo, node, static_cast<int>(i)));
275 }
276 return result;
277}
278
288inline auto
289distribute_affinities_by_numa(size_t num_threads) -> std::vector<thread_affinity>
290{
291 return distribute_affinities_by_numa(read_topology(), num_threads);
292}
293
294} // namespace threadschedule::advanced
Validated CPU identifier.
Definition cpu_id.hpp:23
void add_cpu(cpu_id cpu)
Add a CPU to the affinity set.
auto parse_index_list(std::string_view input) -> std::vector< int >
auto read_index_list(std::string const &path) -> std::vector< int >
auto affinity_for_node(cpu_topology const &topo, int node_index, int thread_index, int threads_per_node=1) -> thread_affinity
Build a thread_affinity for the given NUMA node using a pre-read topology.
auto read_topology() -> cpu_topology
Discover basic topology. Linux: reads /sys for NUMA nodes. Windows: single node, processor-group-awar...
auto distribute_affinities_by_numa(cpu_topology const &topo, size_t num_threads) -> std::vector< thread_affinity >
Distribute thread affinities across NUMA nodes in round-robin order.
expected< T, std::error_code > result
Standard result type used by public APIs.
Definition result.hpp:22
Snapshot of basic CPU/NUMA topology.
std::vector< std::vector< cpu_id > > node_to_cpus