Skip to content

Commit 392065a

Browse files
authored
Merge pull request #116 from carstenbauer/cb/setaffinities
Add setaffinities and setaffinities_cpuids
2 parents 377644d + 89e6e4b commit 392065a

7 files changed

Lines changed: 161 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
ThreadPinning.jl Changelog
22
=========================
33

4+
Version 1.1
5+
-------------
6+
- ![Feature][badge-feature] Added `setaffinities` and `setaffinities_cpuids` as multi-thread analogs of `setaffinity` and `setaffinity_cpuids`, respectively. They allow setting the affinity of all Julia threads at once, similar to how `pinthreads` relates to `pinthread`. A single mask/cpuid vector can be passed to broadcast the same affinity to all threads (e.g. `setaffinities_cpuids(numa(1))`).
7+
48
Version 1.0
59
-------------
610
- ![Feature][badge-feature] OpenBLAS: Almost all pinning and querying functions now have `openblas_*` analogs that provide (almost) all of the same features as for regular Julia threads. Example: `openblas_pinthreads(:cores)` now works. You can also visualize the placement of OpenBLAS threads via `threadinfo(; blas=true)`. These functions are now also part of the official API (and SemVer).

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ThreadPinning"
22
uuid = "811555cd-349b-4f26-b7bc-1f208b848042"
33
authors = ["Carsten Bauer <crstnbr@gmail.com> and contributors"]
4-
version = "1.0.2"
4+
version = "1.1.0-DEV"
55

66
[deps]
77
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"

docs/src/refs/api_pinning.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ unpinthreads
1010
unpinthread
1111
setaffinity
1212
setaffinity_cpuids
13+
setaffinities
14+
setaffinities_cpuids
1315
```
1416

1517
## Pinning - OpenBLAS

src/ThreadPinning.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ else
2424
unpinthread(args...; kwargs...) = nothing
2525
setaffinity(args...; kwargs...) = nothing
2626
setaffinity_cpuids(args...; kwargs...) = nothing
27+
setaffinities(args...; kwargs...) = nothing
28+
setaffinities_cpuids(args...; kwargs...) = nothing
2729
with_pinthreads(f, args...; kwargs...) = f()
2830
pinthreads_likwidpin(args...; kwargs...) = nothing
2931
mpi_pinthreads(args...; kwargs...) = nothing
@@ -52,7 +54,7 @@ export openblas_getaffinity, openblas_getcpuid, openblas_getcpuids,
5254

5355
## pinning
5456
export pinthread, pinthreads, with_pinthreads, unpinthread, unpinthreads
55-
export setaffinity, setaffinity_cpuids
57+
export setaffinity, setaffinity_cpuids, setaffinities, setaffinities_cpuids
5658
export pinthreads_likwidpin, likwidpin_domains, likwidpin_to_cpuids
5759
export mpi_pinthreads, mpi_getcpuids, mpi_gethostnames, mpi_getlocalrank
5860
export distributed_pinthreads, distributed_getcpuids, distributed_gethostnames,

src/mkl.jl

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
module MKL
22

33
using LinearAlgebra: LinearAlgebra, BLAS
4+
using Libdl: Libdl
45

56
const MKL_PATH = Ref{Union{Nothing, String}}(nothing)
7+
const MKL_HANDLE = Ref{Ptr{Cvoid}}(C_NULL)
68

79
"""
810
Returns the full path to the `libmkl_rt` library if the latter is loaded. Will try to
@@ -11,13 +13,32 @@ locate the library and, if successfull, will cache the result. Throws an error o
1113
To force an update of the cache, provide `force_update=true`.
1214
"""
1315
function mkl_fullpath(; force_update = false)
14-
if isnothing(MKL_PATH[]) || force_update
16+
if isnothing(MKL_PATH[])
17+
# First-time lookup: just cache the path
1518
mklpath = _find_mkl()
1619
MKL_PATH[] = mklpath
20+
elseif force_update
21+
# Force a re-discovery of the path
22+
mklpath = _find_mkl()
23+
if MKL_PATH[] != mklpath
24+
# Path changed: close any existing handle and invalidate cache
25+
if MKL_HANDLE[] != C_NULL
26+
Libdl.dlclose(MKL_HANDLE[])
27+
end
28+
MKL_HANDLE[] = C_NULL
29+
MKL_PATH[] = mklpath
30+
end
1731
end
1832
return MKL_PATH[]
1933
end
2034

35+
function _mkl_handle()
36+
if MKL_HANDLE[] == C_NULL
37+
MKL_HANDLE[] = Libdl.dlopen(mkl_fullpath())
38+
end
39+
return MKL_HANDLE[]
40+
end
41+
2142
"""
2243
Check whether Intel MKL is currently loaded via libblastrampoline
2344
"""
@@ -41,15 +62,15 @@ end
4162
mkl_get_dynamic()
4263
Wrapper around the MKL function [`mkl_get_dynamic`](https://www.intel.com/content/www/us/en/develop/documentation/onemkl-developer-reference-fortran/top/support-functions/threading-control/mkl-get-dynamic.html).
4364
"""
44-
mkl_get_dynamic() = @ccall mkl_fullpath().mkl_get_dynamic()::Cint
65+
mkl_get_dynamic() = ccall(Libdl.dlsym(_mkl_handle(), :mkl_get_dynamic), Cint, ())
4566

4667
"""
4768
mkl_set_dynamic(flag::Integer)
4869
4970
Wrapper around the MKL function [`mkl_set_dynamic`](https://www.intel.com/content/www/us/en/develop/documentation/onemkl-developer-reference-c/top/support-functions/threading-control/mkl-set-dynamic.html).
5071
"""
5172
function mkl_set_dynamic(flag::Integer)
52-
@ccall mkl_fullpath().MKL_Set_Dynamic(flag::Cint)::Cvoid
73+
ccall(Libdl.dlsym(_mkl_handle(), :MKL_Set_Dynamic), Cvoid, (Cint,), flag)
5374
end
5475

5576
end # module

src/pinning.jl

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ function unpinthreads end
152152
Set the affinity of a Julia thread to the given CPU-threads.
153153
154154
*Examples:*
155-
* `setaffinity(socket(1))` # set the affinity to the first socket
156-
* `setaffinity(numa(2))` # set the affinity to the second NUMA domain
157-
* `setaffinity(socket(1, 1:3))` # set the affinity to the first three cores in the first NUMA domain
158-
* `setaffinity([1,3,5])` # set the affinity to the CPU-threads with the IDs 1, 3, and 5.
155+
* `setaffinity_cpuids(socket(1))` # set the affinity to the first socket
156+
* `setaffinity_cpuids(numa(2))` # set the affinity to the second NUMA domain
157+
* `setaffinity_cpuids(socket(1, 1:3))` # set the affinity to the first three cores in the first NUMA domain
158+
* `setaffinity_cpuids([1,3,5])` # set the affinity to the CPU-threads with the IDs 1, 3, and 5.
159159
"""
160160
function setaffinity_cpuids end
161161

@@ -166,6 +166,29 @@ Set the affinity of a Julia thread based on the given mask (a vector of ones and
166166
"""
167167
function setaffinity end
168168

169+
"""
170+
Set the affinity of multiple Julia threads to the given CPU-threads.
171+
172+
`cpuids_vec` should be a vector of CPU ID vectors (one per Julia thread), or a single
173+
CPU ID vector to set all threads to the same affinity.
174+
175+
*Examples:*
176+
* `setaffinities_cpuids(numa(1))` # set all threads' affinity to the first NUMA domain
177+
* `setaffinities_cpuids([socket(1), socket(2)])` # set first thread's affinity to socket 1, second to socket 2
178+
* `setaffinities_cpuids([numa(1), numa(2)])` # set affinities across NUMA domains
179+
"""
180+
function setaffinities_cpuids end
181+
182+
"""
183+
setaffinities(masks; threadpool = :default)
184+
185+
Set the affinity of multiple Julia threads based on the given masks.
186+
187+
`masks` should be a vector of affinity masks (one per Julia thread), or a single mask to
188+
set all threads to the same affinity. Each mask is a vector of ones and zeros.
189+
"""
190+
function setaffinities end
191+
169192
# OpenBLAS
170193
"""
171194
openblas_setaffinity(mask; threadid)
@@ -230,7 +253,7 @@ function openblas_unpinthreads end
230253
module Pinning
231254

232255
import ThreadPinning: pinthread, pinthreads, with_pinthreads, unpinthread, unpinthreads
233-
import ThreadPinning: setaffinity, setaffinity_cpuids
256+
import ThreadPinning: setaffinity, setaffinity_cpuids, setaffinities, setaffinities_cpuids
234257
import ThreadPinning: openblas_setaffinity, openblas_setaffinity_cpuids,
235258
openblas_pinthread, openblas_pinthreads,
236259
openblas_unpinthread, openblas_unpinthreads
@@ -282,6 +305,41 @@ function setaffinity_cpuids(cpuids::AbstractVector{<:Integer}; kwargs...)
282305
return
283306
end
284307

308+
function setaffinities(masks::AbstractVector{<:AbstractVector{<:Integer}};
309+
threadpool::Symbol = :default)
310+
tids = ThreadPinningCore.threadids(; threadpool)
311+
length(masks) == length(tids) ||
312+
throw(ArgumentError("Number of masks ($(length(masks))) must match the number of " *
313+
"threads ($(length(tids))) in the `$(threadpool)` threadpool."))
314+
for (i, threadid) in pairs(tids)
315+
setaffinity(copy(masks[i]); threadid)
316+
end
317+
return
318+
end
319+
320+
function setaffinities(mask::AbstractVector{<:Integer}; threadpool::Symbol = :default, kwargs...)
321+
tids = ThreadPinningCore.threadids(; threadpool)
322+
setaffinities([mask for _ in tids]; threadpool, kwargs...)
323+
end
324+
325+
function setaffinities_cpuids(cpuids_vec::AbstractVector{<:AbstractVector{<:Integer}};
326+
threadpool::Symbol = :default)
327+
tids = ThreadPinningCore.threadids(; threadpool)
328+
length(cpuids_vec) == length(tids) ||
329+
throw(ArgumentError("Number of CPU ID vectors ($(length(cpuids_vec))) must match " *
330+
"the number of threads ($(length(tids))) in the " *
331+
"`$(threadpool)` threadpool."))
332+
for (i, threadid) in pairs(tids)
333+
setaffinity_cpuids(cpuids_vec[i]; threadid)
334+
end
335+
return
336+
end
337+
338+
function setaffinities_cpuids(cpuids::AbstractVector{<:Integer}; threadpool::Symbol = :default, kwargs...)
339+
tids = ThreadPinningCore.threadids(; threadpool)
340+
setaffinities_cpuids([cpuids for _ in tids]; threadpool, kwargs...)
341+
end
342+
285343
function openblas_setaffinity_cpuids(cpuids::AbstractVector{<:Integer}; kwargs...)
286344
_check_cpuids(cpuids)
287345
mask = Utility.cpuids2affinitymask(cpuids)

test/tests_pinning.jl

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,70 @@ function pinning_tests()
181181
@test isnothing(setaffinity_cpuids([cpuid2, cpuid1]; threadid = randtid))
182182
end
183183

184+
@testset "setaffinities" begin
185+
tids = ThreadPinning.threadids(; threadpool = :default)
186+
nt = length(tids)
187+
188+
@testset "setaffinities (vector of masks)" begin
189+
cpuid1, cpuid2 = get_two_cpuids()
190+
pinthreads([isodd(i) ? cpuid1 : cpuid2 for i in 1:nt]; warn = false)
191+
masks = [getaffinity(; threadid = tid) for tid in tids]
192+
expected_cpuids = getcpuids()
193+
pinthreads(:random; warn = false)
194+
@test isnothing(setaffinities(masks))
195+
for (j, tid) in pairs(tids)
196+
@test getaffinity(; threadid = tid) == masks[j]
197+
end
198+
@test getcpuids() == expected_cpuids
199+
end
200+
201+
@testset "setaffinities (single mask broadcast)" begin
202+
cpuid1, _ = get_two_cpuids()
203+
pinthreads(fill(cpuid1, nt); warn = false)
204+
mask = getaffinity(; threadid = firsttid)
205+
pinthreads(:random; warn = false)
206+
@test isnothing(setaffinities(mask))
207+
@test all(tid -> getaffinity(; threadid = tid) == mask, tids)
208+
@test all(i -> getcpuid(; threadid = i) == cpuid1, tids)
209+
end
210+
211+
@testset "setaffinities_cpuids (vector of cpuid vectors)" begin
212+
cpuid1, cpuid2 = get_two_cpuids()
213+
cpuids_vec = fill([cpuid1], nt)
214+
cpuids_vec[2] = [cpuid2]
215+
@test isnothing(setaffinities_cpuids(cpuids_vec))
216+
@test all(tid -> ispinned(; threadid = tid), tids)
217+
# Under faking, cpuids2affinitymask maps by list position, so
218+
# getcpuid may not match the requested cpuid on systems with
219+
# non-contiguous CPU IDs (e.g. FUGAKU). Verify cpuids on host only.
220+
if !ThreadPinning.Faking.isfaking()
221+
@test getcpuid(; threadid = tids[1]) == cpuid1
222+
@test getcpuid(; threadid = tids[2]) == cpuid2
223+
end
224+
end
225+
226+
@testset "setaffinities_cpuids (single cpuid vector broadcast)" begin
227+
cpuid1, _ = get_two_cpuids()
228+
@test isnothing(setaffinities_cpuids([cpuid1]))
229+
@test all(tid -> ispinned(; threadid = tid), tids)
230+
# Same caveat as above: cpuids2affinitymask position mismatch under faking.
231+
if !ThreadPinning.Faking.isfaking()
232+
@test all(i -> getcpuid(; threadid = i) == cpuid1, tids)
233+
end
234+
end
235+
236+
@testset "setaffinities error: wrong number of masks" begin
237+
masks = [getaffinity(; threadid = i) for i in tids]
238+
@test_throws ArgumentError setaffinities(masks[1:end-1])
239+
end
240+
241+
@testset "setaffinities_cpuids error: wrong number of cpuid vectors" begin
242+
cpuid1, _ = get_two_cpuids()
243+
cpuids_vec = fill([cpuid1], nt)
244+
@test_throws ArgumentError setaffinities_cpuids(cpuids_vec[1:end-1])
245+
end
246+
end
247+
184248
@testset "force = false" begin
185249
cpuid1, cpuid2 = get_two_cpuids()
186250
ThreadPinningCore.Internals.forget_pin_attempts()

0 commit comments

Comments
 (0)