-
Notifications
You must be signed in to change notification settings - Fork 19
Add Windows thread affinity support #338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,121 @@ | ||||||||||
| #pragma once | ||||||||||
|
|
||||||||||
| // This file provides an adaptation layer that lets the Celerity runtime use the Windows API for thread affinity through the POSIX-like | ||||||||||
| // cpu_set_t/sched_*affinity/pthread_*affinity_np interface expected by the rest of the runtime. cpu_set_t covers up to 1024 logical processors, | ||||||||||
| // spanning multiple Windows processor groups (up to 16 groups of 64 processors each). | ||||||||||
| // | ||||||||||
| // Note: Windows' GROUP_AFFINITY API can only bind a thread to a single processor group at a time. If a cpu_set_t's bits span more than one | ||||||||||
| // group, cpuset_to_group_affinity() cannot represent that as one affinity mask; it logs a warning and fails in that case. | ||||||||||
| // TODO: If we ever want to support pinning to multiple groups, we would need to find a way around this limitation. | ||||||||||
|
|
||||||||||
| #include <bit> | ||||||||||
| #include <cstdint> | ||||||||||
| #include <cstring> | ||||||||||
| #include <vector> | ||||||||||
|
|
||||||||||
| #ifdef _WIN32 | ||||||||||
| // Prevent Windows headers from polluting global namespace with min/max macros. | ||||||||||
| #ifndef NOMINMAX | ||||||||||
| #define NOMINMAX | ||||||||||
| #endif | ||||||||||
| #ifndef WIN32_LEAN_AND_MEAN | ||||||||||
| #define WIN32_LEAN_AND_MEAN | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
| #include <windows.h> | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
| #include "log.h" | ||||||||||
|
|
||||||||||
| using pthread_t = DWORD; | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
|
|
||||||||||
| struct cpu_set_t { | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
| static constexpr unsigned CPU_SETSIZE = 1024; | ||||||||||
| static constexpr unsigned WORDS = (CPU_SETSIZE + 63) / 64; | ||||||||||
|
|
||||||||||
| uint64_t bits[WORDS] = {}; | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE; | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
|
|
||||||||||
| void CPU_ZERO(cpu_set_t* set); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
| void CPU_SET(unsigned cpu, cpu_set_t* set); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
| void CPU_CLR(unsigned cpu, cpu_set_t* set); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
| int CPU_ISSET(unsigned cpu, const cpu_set_t* set); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
|
|
||||||||||
| int CPU_COUNT(const cpu_set_t* set); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
| int CPU_EQUAL(const cpu_set_t* a, const cpu_set_t* b); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
|
|
||||||||||
| int sched_getaffinity(int pid, size_t cpusetsize, cpu_set_t* mask); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| int sched_setaffinity(int pid, size_t cpusetsize, const cpu_set_t* mask); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
|
||||||||||
| pthread_t pthread_self(); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
|
||||||||||
| int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t* mask); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t* mask); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
|
||||||||||
| namespace win32_pthread_detail { | ||||||||||
|
|
||||||||||
| static constexpr unsigned PROCS_PER_GROUP = 64; | ||||||||||
|
|
||||||||||
| struct cpu_topology_entry { | ||||||||||
| WORD group; | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
| WORD count; | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌
Comment on lines
+62
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| }; | ||||||||||
|
|
||||||||||
| using cpu_topology = std::vector<cpu_topology_entry>; | ||||||||||
|
|
||||||||||
| struct windows_topology_policy { | ||||||||||
| static WORD get_group_count() { return GetActiveProcessorGroupCount(); } | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ clang-diagnostic-error ❌ |
||||||||||
|
|
||||||||||
| static unsigned get_proc_count(WORD g) { return GetActiveProcessorCount(g); } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| using topology_policy = windows_topology_policy; | ||||||||||
| template <typename Policy = topology_policy> | ||||||||||
| cpu_topology get_cpu_topology() { | ||||||||||
| cpu_topology topo; | ||||||||||
|
|
||||||||||
| const WORD groups = Policy::get_group_count(); | ||||||||||
|
|
||||||||||
| for(WORD g = 0; g < groups; ++g) { | ||||||||||
| const unsigned count = Policy::get_proc_count(g); | ||||||||||
| topo.push_back({g, (WORD)count}); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return topo; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| template <typename Policy = windows_topology_policy> | ||||||||||
| bool cpuset_to_group_affinity(const cpu_set_t* set, GROUP_AFFINITY& out) { | ||||||||||
| const WORD groups = Policy::get_group_count(); | ||||||||||
|
|
||||||||||
| bool found = false; | ||||||||||
|
|
||||||||||
| for(WORD g = 0; g < groups; ++g) { | ||||||||||
| const unsigned procs = Policy::get_proc_count(g); | ||||||||||
|
|
||||||||||
| KAFFINITY mask = 0; | ||||||||||
|
|
||||||||||
| for(unsigned p = 0; p < procs && p < PROCS_PER_GROUP; ++p) { | ||||||||||
| const unsigned global_cpu = g * PROCS_PER_GROUP + p; | ||||||||||
|
|
||||||||||
| if(CPU_ISSET(global_cpu, set)) { mask |= (KAFFINITY(1) << p); } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if(mask == 0) continue; | ||||||||||
|
|
||||||||||
| if(found) { | ||||||||||
| CELERITY_WARN("Affinity mask spans multiple processor groups (not supported on Windows)."); | ||||||||||
| return false; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| found = true; | ||||||||||
| out.Group = g; | ||||||||||
| out.Mask = mask; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return found; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| } // namespace win32_pthread_detail | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,8 @@ | ||
| #include "affinity.h" | ||
|
|
||
| #include "log.h" | ||
|
|
||
| #include <cassert> | ||
|
|
||
|
|
||
| namespace celerity::detail::thread_pinning { | ||
|
|
||
| thread_pinner::thread_pinner(const runtime_configuration& cfg) { | ||
| if(cfg.enabled) { CELERITY_WARN("Thread pinning is currently not supported on Windows."); } | ||
| } | ||
| thread_pinner::~thread_pinner() {} | ||
|
|
||
| } // namespace celerity::detail::thread_pinning | ||
| // The Windows thread-pinning logic is identical to the POSIX one in affinity.unix.cc. affinity_win32_adapter.h/.cc | ||
| // reimplement the POSIX cpu_set_t / pthread_t / CPU_*() / sched_*affinity() / pthread_*affinity_np() surface on top | ||
| // of the Win32 GROUP_AFFINITY API, so rather than duplicating affinity.unix.cc we just pull it in here: its own | ||
| // _WIN32 branch picks up the adapter header instead of <pthread.h>/<sched.h>, and the shared logic below that | ||
| // compiles unchanged. | ||
| #include "platform_specific/affinity_win32_adapter.h" | ||
|
|
||
| #include "affinity.unix.cc" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| #ifdef _WIN32 | ||
| #include "platform_specific/affinity_win32_adapter.h" | ||
|
|
||
| #include <algorithm> | ||
| #include <numeric> | ||
|
|
||
| #include "log.h" | ||
|
|
||
| void CPU_ZERO(cpu_set_t* set) { std::memset(set->bits, 0, sizeof(set->bits)); } | ||
|
|
||
| void CPU_SET(unsigned cpu, cpu_set_t* set) { | ||
| if(cpu < cpu_set_t::CPU_SETSIZE) set->bits[cpu / 64] |= (uint64_t(1) << (cpu % 64)); | ||
| } | ||
|
|
||
| void CPU_CLR(unsigned cpu, cpu_set_t* set) { | ||
| if(cpu < cpu_set_t::CPU_SETSIZE) set->bits[cpu / 64] &= ~(uint64_t(1) << (cpu % 64)); | ||
| } | ||
|
|
||
| int CPU_ISSET(unsigned cpu, const cpu_set_t* set) { | ||
| if(cpu >= cpu_set_t::CPU_SETSIZE) return 0; | ||
| return (set->bits[cpu / 64] & (uint64_t(1) << (cpu % 64))) != 0; | ||
| } | ||
|
|
||
| int CPU_COUNT(const cpu_set_t* set) { | ||
| int n = 0; | ||
| for(unsigned i = 0; i < cpu_set_t::WORDS; ++i) | ||
| n += std::popcount(set->bits[i]); | ||
| return n; | ||
| } | ||
|
|
||
| int CPU_EQUAL(const cpu_set_t* a, const cpu_set_t* b) { return std::memcmp(a->bits, b->bits, sizeof(a->bits)) == 0; } | ||
|
|
||
|
|
||
| namespace win32_pthread_detail {} // namespace win32_pthread_detail | ||
|
|
||
| int sched_getaffinity(int, size_t, cpu_set_t* mask) { | ||
| GROUP_AFFINITY ga{}; | ||
|
|
||
| if(!GetThreadGroupAffinity(GetCurrentThread(), &ga)) return -1; | ||
|
|
||
| CPU_ZERO(mask); | ||
|
|
||
| const unsigned base = ga.Group * win32_pthread_detail::PROCS_PER_GROUP; | ||
|
|
||
| for(unsigned p = 0; p < win32_pthread_detail::PROCS_PER_GROUP; ++p) { | ||
| if(ga.Mask & (KAFFINITY(1) << p)) CPU_SET(base + p, mask); | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int sched_setaffinity(int, size_t, const cpu_set_t* mask) { | ||
| GROUP_AFFINITY ga{}; | ||
|
|
||
| if(!win32_pthread_detail::cpuset_to_group_affinity(mask, ga)) { | ||
| CELERITY_WARN("sched_setaffinity failed (multi-group mask rejected)"); | ||
| return -1; | ||
| } | ||
|
|
||
| return SetThreadGroupAffinity(GetCurrentThread(), &ga, nullptr) ? 0 : -1; | ||
| } | ||
|
|
||
| pthread_t pthread_self() { return GetCurrentThreadId(); } | ||
|
|
||
| int pthread_setaffinity_np(pthread_t thread, size_t, const cpu_set_t* mask) { | ||
| const bool self = (thread == GetCurrentThreadId()); | ||
|
|
||
| HANDLE h = self ? GetCurrentThread() : OpenThread(THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, thread); | ||
|
|
||
| if(!h) return -1; | ||
|
|
||
| GROUP_AFFINITY ga{}; | ||
|
|
||
| if(!win32_pthread_detail::cpuset_to_group_affinity(mask, ga)) { | ||
| if(!self) CloseHandle(h); | ||
| return -1; | ||
| } | ||
|
|
||
| int ok = SetThreadGroupAffinity(h, &ga, nullptr) ? 0 : -1; | ||
|
|
||
| if(!self) CloseHandle(h); | ||
| return ok; | ||
| } | ||
|
|
||
| int pthread_getaffinity_np(pthread_t thread, size_t, cpu_set_t* mask) { | ||
| const bool self = (thread == GetCurrentThreadId()); | ||
|
|
||
| HANDLE h = self ? GetCurrentThread() : OpenThread(THREAD_QUERY_INFORMATION, FALSE, thread); | ||
|
|
||
| if(!h) return -1; | ||
|
|
||
| GROUP_AFFINITY ga{}; | ||
|
|
||
| if(!GetThreadGroupAffinity(h, &ga)) { | ||
| if(!self) CloseHandle(h); | ||
| return -1; | ||
| } | ||
|
|
||
| CPU_ZERO(mask); | ||
|
|
||
| const unsigned base = ga.Group * win32_pthread_detail::PROCS_PER_GROUP; | ||
|
|
||
| for(unsigned p = 0; p < win32_pthread_detail::PROCS_PER_GROUP; ++p) { | ||
| if(ga.Mask & (KAFFINITY(1) << p)) CPU_SET(base + p, mask); | ||
| } | ||
|
|
||
| if(!self) CloseHandle(h); | ||
| return 0; | ||
| } | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should have a comment at the start explaining its purpose (i.e. implementing unix-like affinity using windows APIs).
maybe the name should also be something like
affinity_win32_adapter?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!