Skip to content

Add setaffinities and setaffinities_cpuids#116

Merged
carstenbauer merged 14 commits intomainfrom
cb/setaffinities
Mar 26, 2026
Merged

Add setaffinities and setaffinities_cpuids#116
carstenbauer merged 14 commits intomainfrom
cb/setaffinities

Conversation

@carstenbauer
Copy link
Copy Markdown
Owner

@carstenbauer carstenbauer commented Mar 26, 2026

  • Restrict setaffinities(masks; ...) signature to AbstractVector{<:AbstractVector{<:Integer}}

⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.

carstenbauer and others added 8 commits March 26, 2026 14:51
Add `setaffinities(masks; threadpool)` and `setaffinities_cpuids(cpuids_vec; threadpool)`
as multi-thread pendants of `setaffinity` / `setaffinity_cpuids`, analogous to the
relationship between `pinthread` and `pinthreads`. Fixes #115.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- setaffinities(mask) and setaffinities_cpuids(cpuids) now accept a single
  mask/cpuid-vector and broadcast it to all threads in the pool
- Add unit tests covering vector and broadcast variants of both functions,
  as well as ArgumentError on length mismatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The kernel may normalize affinity masks (e.g. strip trailing zeros) after
a setaffinity syscall, so comparing raw mask bytes before and after is
unstable across systems. Follow the existing setaffinity test style and
only assert isnothing(...).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of only checking isnothing(...), pin to single-CPU masks/cpuid vectors
and verify getcpuid() returns the expected CPU for each thread — the same
approach used by pinthreads tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ThreadPinningCore.setaffinity(mask) bypasses the virtual CPU state that
faking maintains for pinthreads/getcpuid. Under faking, getcpuid() returns
the virtual fake CPU ID (unchanged by setaffinity), so exact-CPU checks
after setaffinities/setaffinities_cpuids produce false failures on fake
systems like FUGAKU. Guard those checks with !Faking.isfaking(), matching
the existing precedent for checks that don't apply under faking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cpuids2affinitymask maps CPU IDs to positions in ThreadPinning.cpuids().
For systems with non-contiguous IDs (FUGAKU: 0,1,12,...,59), position 10
corresponds to CPU 19, not CPU 9. Under faking on a host with contiguous
IDs, ThreadPinningCore.setaffinity interprets position 10 as real CPU 9,
causing the mismatch (9 == 19 on FUGAKU, passes on other fake systems).

Fix: get masks via getaffinity() after a pinthreads call instead, so the
masks are in the real-host format that ThreadPinningCore.setaffinity expects.
Keep !isfaking() guard on getcpuid() checks since setaffinity does not
update the faking virtual state that getcpuid() reads from.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- setaffinities (mask) tests: after applying saved masks, verify via
  getaffinity() that the real OS masks match (getaffinity is not faked,
  so this works on both real and fake systems). getcpuids() check
  remains guarded by !isfaking() since getcpuid is faked.
- setaffinities_cpuids tests: add ispinned() checks (reads real OS)
  to confirm each thread was actually pinned to a single CPU, even under
  faking where we can't verify which CPU via getcpuid.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@carstenbauer
Copy link
Copy Markdown
Owner Author

carstenbauer commented Mar 26, 2026

Specifically, you get the behavior desired in #115 by calling e.g.

setaffinities_cpuids(numa(1)) # will set the affinities of all Julia threads to the first NUMA domain

carstenbauer and others added 4 commits March 26, 2026 19:33
ThreadPinningCore.setaffinity(mask) extends the mask in-place via append!
when mask length < uv_cpumask_size(). This caused setaffinities to mutate
the caller's mask objects, leading to test failures when comparing the
mutated masks (now extended to uv_cpumask_size) against fresh getaffinity()
results (truncated to cpuidlimit/Sys.CPU_THREADS).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The faking layer is self-consistent for mask-based operations:
setaffinity updates fake cpuid from mask bit position, and getaffinity
reconstructs the mask from the fake cpuid. So the mask round-trip and
getcpuid checks work correctly on all fake systems.

The cpuid-based tests (setaffinities_cpuids) still need the guards
because cpuids2affinitymask maps by list position, which diverges from
the faking convention (cpuid = bit position - 1) on systems with
non-contiguous CPU IDs like FUGAKU.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Julia 1.13 no longer accepts a function call as the library expression
in @CCall (TypeError: expected Symbol, got Expr). Replace with explicit
Libdl.dlsym/dlopen using a cached library handle to avoid repeated
lookups.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@carstenbauer carstenbauer mentioned this pull request Mar 26, 2026
@carstenbauer carstenbauer marked this pull request as ready for review March 26, 2026 18:53
@carstenbauer carstenbauer requested a review from Copilot March 26, 2026 18:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds multi-thread variants of the existing affinity-setting APIs so callers can set the affinity masks (or CPU ID sets) for an entire Julia threadpool at once, addressing the thread-affinity inheritance problem described in #115.

Changes:

  • Introduces setaffinities(masks; threadpool) and setaffinities_cpuids(cpuids_vec; threadpool) APIs (with “single value broadcast” behavior).
  • Adds unit tests and API reference docs for the new functions.
  • Updates MKL symbol lookup to use an explicit cached dlopen handle; bumps version/changelog.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/pinning.jl Adds public API declarations + implementations for setaffinities / setaffinities_cpuids.
test/tests_pinning.jl Adds test coverage for vector-per-thread and broadcast forms + error cases.
src/ThreadPinning.jl Exports the new APIs and defines non-Linux no-op fallbacks.
docs/src/refs/api_pinning.md Adds the new APIs to the generated pinning API reference list.
src/mkl.jl Switches MKL calls to dlsym off a cached dlopen handle; invalidates cache on path refresh.
Project.toml Bumps version to 1.1.0-DEV.
CHANGELOG.md Adds a 1.1 entry describing the new APIs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pinning.jl
Comment thread src/pinning.jl
Comment thread src/pinning.jl Outdated
Comment thread src/mkl.jl Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ector{<:Integer}}

Agent-Logs-Url: https://github.com/carstenbauer/ThreadPinning.jl/sessions/699ce3ff-9636-490c-a773-517e3f193554

Co-authored-by: carstenbauer <187980+carstenbauer@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 26, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/JuliaBinaryWrappers/Hwloc_jll.jl/tarball/baaaebd42ed9ee1bd9173cfd56910e55a8622ee1
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/IntelOpenMP_jll.jl/tarball/10bd689145d2c3b2a9844005d01087cc1194e79e
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/Libiconv_jll.jl/tarball/be484f5c92fad0bd8acfef35fe017900b0b73809
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/MKL_jll.jl/tarball/f046ccd0c6db2832a9f639e2c669c6fe867e5f4f
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/MPIABI_jll.jl/tarball/fdc0f7ca9e7e645d1114b93c9eee98f352b59a0c
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/MPICH_jll.jl/tarball/fec645c5b0306bc15ed1b02bd24255ef16a1c1dd
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/MPItrampoline_jll.jl/tarball/36c2d142e7d45fb98b5f83925213feb3292ca348
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/MicrosoftMPI_jll.jl/tarball/bc95bf4149bf535c09602e3acdf950d9b4376227
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/OpenMPI_jll.jl/tarball/2f3d05e419b6125ffe06e55784102e99325bdbe2
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/XML2_jll.jl/tarball/80d3930c6347cfce7ccf96bd3bafdf079d9c0390
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/Xorg_libpciaccess_jll.jl/tarball/4909eb8f1cbf6bd4b1c30dd18b2ead9019ef2fad
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/oneTBB_jll.jl/tarball/1350188a69a6e46f799d3945beef36435ed7262f
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaData/DelimitedFiles.jl/tarball/9e2f36d3c96a820c678f2f1f1782582fcf685bae
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaDocs/DocStringExtensions.jl/tarball/7442a5dfe1ebb773c29cc2962a8980f47221d76c
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaFolds2/StableTasks.jl/tarball/c4f6610f85cb965bee5bfafa64cbeeda55a4e0b2
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaInterop/CEnum.jl/tarball/389ad5c84de1ae7cf0e28e381131c98ea87d54fc
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaLang/PrecompileTools.jl/tarball/07a921781cab75691315adc645096ed5e370cb77
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaLinearAlgebra/MKL.jl/tarball/17f06b6fb5198b757413d8379bfa62246c5da5a3
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaPackaging/JLLWrappers.jl/tarball/0533e564aae234aff59ab625543145446d8b6ec2
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaPackaging/Preferences.jl/tarball/8b770b60760d4451834fe79dd483e318eee709c4
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaPackaging/Requires.jl/tarball/62389eeff14780bfe55195b7204c0d8738436d64
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaParallel/Hwloc.jl/tarball/6a3d80f31ff87bc94ab22a7b8ec2f263f9a6a583
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaParallel/MPI.jl/tarball/8e98d5d80b87403c311fd51e8455d4546ba7a5f8
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaParallel/MPI.jl/tarball/f53e40f779b860bd813441a3b6bc434f48c99ee1
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStats/Statistics.jl/tarball/ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/KlausC/PkgVersion.jl/tarball/f9501cc0430a26bc3d156ae1b5b0c1b47af4d6da
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/carstenbauer/SysInfo.jl/tarball/7aaebfbf5b3a39268f4a0caaa43e878e1138d25c
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/carstenbauer/ThreadPinningCore.jl/tarball/bb3c6f3b5600fbff028c43348365681b34d06499
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/julia-testitems/TestItems.jl/tarball/42fd9023fef18b9b78c8343a4e2f3813ffbcefcb
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/julia-vscode/TestItemRunner.jl/tarball/0a4cba9133b1a19fcf6175d0affde1418fc46fa2
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (http block)
  • pkg.julialang.org
    • Triggering command: /usr/bin/julia julia --project -e using Pkg; Pkg.test() (dns block)
    • Triggering command: /usr/local/julia1.12.5/bin/julia /usr/local/julia1.12.5/bin/julia -C native -J/usr/local/julia1.12.5/lib/julia/sys.so -g1 --code-coverage=none --color=auto --check-bounds=yes --warn-overwrite=yes --depwarn=yes --inline=yes --startup-file=no --track-allocation=none --check-bounds=yes --output-o /home/REDACTED/.julia/compiled/v1.12/MKL_jll/jl_3OHhLE --output-ji /home/REDACTED/.julia/compiled/v1.12/MKL_jll/jl_K1wzzo --output-incremental=yes --startup-file=no (dns block)
    • Triggering command: /usr/local/julia1.12.5/bin/julia /usr/local/julia1.12.5/bin/julia -C native -J/usr/local/julia1.12.5/lib/julia/sys.so -g1 --code-coverage=none --color=auto --check-bounds=yes --warn-overwrite=yes --depwarn=yes --inline=yes --startup-file=no --track-allocation=none --check-bounds=yes --output-o /home/REDACTED/.julia/compiled/v1.12/MKL/jl_NC4GI1 --output-ji /home/REDACTED/.julia/compiled/v1.12/MKL/jl_xzp0J7 --output-incremental=yes --startup-file=no (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@carstenbauer carstenbauer merged commit 392065a into main Mar 26, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants