Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic
Versioning](http://semver.org/spec/v2.0.0.html).

## [WIP]

This release is WIP

### Added

- Add preliminary support for thread affinity on Windows by emulating the POSIX interface (#338)

## [0.7.0] - 2025-08-18

This release includes changes that may require adjustments when upgrading:
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ set(CELERITY_DETAIL_HAS_NAMED_THREADS OFF)

if(WIN32)
set(SOURCES ${SOURCES} src/platform_specific/affinity.win.cc)
set(SOURCES ${SOURCES} src/platform_specific/affinity_win32.cc)
set(SOURCES ${SOURCES} src/platform_specific/named_threads.win.cc)
set(CELERITY_DETAIL_HAS_NAMED_THREADS ON)
elseif(UNIX)
Expand Down
4 changes: 4 additions & 0 deletions include/affinity.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

#include "named_threads.h"

#ifdef _WIN32
#include "platform_specific/affinity_win32.h"
#endif

// The goal of this thread pinning mechanism, when enabled, is to ensure that threads which benefit from fast communication
// are pinned to cores that are close to each other in terms of cache hierarchy.
// It currently accomplishes this by pinning threads to cores in a round-robin fashion according to their order in the `named_threads::thread_type` enum.
Expand Down
105 changes: 105 additions & 0 deletions include/platform_specific/affinity_win32.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#define WIN32_LEAN_AND_MEAN
#define NOMINMAX

#include <bit>
#include <cstdint>
#include <cstring>
#include <vector>
#include <windows.h>

#include "log.h"

using pthread_t = DWORD;

struct cpu_set_t {
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;

void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);
void CPU_CLR(unsigned cpu, cpu_set_t* set);
int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

int CPU_COUNT(const cpu_set_t* set);
int CPU_EQUAL(const cpu_set_t* a, const cpu_set_t* b);

int sched_getaffinity(int pid, size_t cpusetsize, cpu_set_t* mask);
int sched_setaffinity(int pid, size_t cpusetsize, const cpu_set_t* mask);

pthread_t pthread_self();

int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t* mask);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t* mask);

namespace win32_pthread_detail {

static constexpr unsigned PROCS_PER_GROUP = 64;

struct cpu_topology_entry {
WORD group;
WORD count;
};

using cpu_topology = std::vector<cpu_topology_entry>;

struct windows_topology_policy {
static WORD get_group_count() { return GetActiveProcessorGroupCount(); }

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
Loading
Loading