Skip to content
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

add BLAS.get_num_threads #36360

Merged
merged 30 commits into from
Jun 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bea54cc
add BLAS.get_num_threads
jw3126 Jun 19, 2020
2de23cd
fix
jw3126 Jun 19, 2020
0e6f8fb
fix
jw3126 Jun 19, 2020
9f396c2
fix
jw3126 Jun 19, 2020
e84c258
fix
jw3126 Jun 19, 2020
9a0edcb
warn if get/set of num_bals_threads fails
jw3126 Jun 19, 2020
f6daa79
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
8b2c8c4
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
a15f851
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
826d8ff
fix
jw3126 Jun 20, 2020
ce61636
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
b38afaa
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
0ee0efa
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
8e4fedd
fix
jw3126 Jun 20, 2020
33a95d5
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
35cf5a6
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
bc370b4
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
dd455b5
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 20, 2020
abd2084
fix
jw3126 Jun 22, 2020
b8e9055
fix
jw3126 Jun 22, 2020
9eabcb6
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 23, 2020
920b90b
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 23, 2020
b011dc6
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 23, 2020
95ccdf9
Update stdlib/LinearAlgebra/test/blas.jl
jw3126 Jun 23, 2020
72ef30e
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 23, 2020
4cf2628
Update stdlib/LinearAlgebra/test/blas.jl
jw3126 Jun 23, 2020
bb69931
Update stdlib/LinearAlgebra/test/blas.jl
jw3126 Jun 23, 2020
06550d6
Update stdlib/LinearAlgebra/src/blas.jl
jw3126 Jun 23, 2020
b6aa076
improve docstrings
jw3126 Jun 23, 2020
c524c53
add to NEWS.md
jw3126 Jun 23, 2020
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
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Standard library changes
#### LinearAlgebra
* New method `LinearAlgebra.issuccess(::CholeskyPivoted)` for checking whether pivoted Cholesky factorization was successful ([#36002]).
* `UniformScaling` can now be indexed into using ranges to return dense matrices and vectors ([#24359]).
* New function `LinearAlgebra.BLAS.get_num_threads()` for getting the number of BLAS threads. ([#36360])

#### Markdown

Expand Down
100 changes: 87 additions & 13 deletions stdlib/LinearAlgebra/src/blas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,27 +106,101 @@ end

openblas_get_config() = strip(unsafe_string(ccall((@blasfunc(openblas_get_config), libblas), Ptr{UInt8}, () )))

function guess_vendor()
# like determine_vendor, but guesses blas in some cases
# where determine_vendor returns :unknown
ret = vendor()
if Sys.isapple() && (ret == :unknown)
ret = :osxblas
end
ret
end


"""
set_num_threads(n)
set_num_threads(n::Integer)
set_num_threads(::Nothing)
Copy link
Member

Choose a reason for hiding this comment

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

This seems like an unusual API, I would prefer set_num_threads() instead of set_num_threads(nothing).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is to allow the pattern

default = BLAS.get_num_threads() # returns nothing on exotic platforms
BLAS.set_num_threads(1)
# do stuff
BLAS.set_num_threads(default)

Copy link
Member

Choose a reason for hiding this comment

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

That will still work if you make the signature set_num_threads(n=nothing)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To me allowing nothing is a hack to allow the above pattern on strange platforms. It is not a thing I would encourage or that I think needs more convenient syntax.

Copy link
Contributor Author

@jw3126 jw3126 Jun 23, 2020

Choose a reason for hiding this comment

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

I imagine if you have code like set_num_threads() it is more likely you forgot to pass the number of threads, than that you really want to invoke the nothing method.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should define set_num_threads() = set_num_threads(nothing). It would send a wrong message that set_num_threads(nothing) is somehow a reasonable default. But it's not. It is the last resort that exists only for supporting the rollback use case.

But this is not clear from the current docstring. I think it's better to clarify this.

Copy link
Contributor

@mcabbott mcabbott Jun 23, 2020

Choose a reason for hiding this comment

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

Perhaps clearest to show the pattern which motivates this:

Set the number of threads the BLAS library should use.

Also accepts `nothing`, in which case it tries to set set the default number of threads.
On exotic variants of BLAS, `nothing` may be returned by ` get_num_threads()`.
Thus the following pattern may fail to set the number of threads, but will not give an error:

old = get_num_threads()
set_num_threads(1)
@threads for i in 1:10
    # single-threaded BLAS calls
end
set_num_threads(old)

Copy link
Member

Choose a reason for hiding this comment

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

@fredrikekre Does the argument here makes sense? It'd be nice if you can have a look at the docstring.


Set the number of threads the BLAS library should use.
Set the number of threads the BLAS library should use equal to `n::Integer`.

Also accepts `nothing`, in which case julia tries to guess the default number of threads.
Passing `nothing` is discouraged and mainly exists for the following reason:

On exotic variants of BLAS, `nothing` may be returned by `get_num_threads()`.
Thus on exotic variants of BLAS, the following pattern may fail to set the number of threads:

```julia
old = get_num_threads()
set_num_threads(1)
@threads for i in 1:10
# single-threaded BLAS calls
end
set_num_threads(old)
```
Because `set_num_threads` accepts `nothing`, this code can still run
on exotic variants of BLAS without error. Warnings will be raised instead.

!!! compat "Julia 1.6"
`set_num_threads(::Nothing)` requires at least Julia 1.6.
"""
function set_num_threads(n::Integer)
blas = vendor()
if blas === :openblas
return ccall((:openblas_set_num_threads, libblas), Cvoid, (Int32,), n)
elseif blas === :openblas64
return ccall((:openblas_set_num_threads64_, libblas), Cvoid, (Int32,), n)
elseif blas === :mkl
set_num_threads(n)::Nothing = _set_num_threads(n)

function _set_num_threads(n::Integer; _blas = guess_vendor())
if _blas === :openblas || _blas == :openblas64
return ccall((@blasfunc(openblas_set_num_threads), libblas), Cvoid, (Cint,), n)
elseif _blas === :mkl
# MKL may let us set the number of threads in several ways
return ccall((:MKL_Set_Num_Threads, libblas), Cvoid, (Cint,), n)
end

# OSX BLAS looks at an environment variable
@static if Sys.isapple()
elseif _blas === :osxblas
# OSX BLAS looks at an environment variable
ENV["VECLIB_MAXIMUM_THREADS"] = n
else
@assert _blas === :unknown
@warn "Failed to set number of BLAS threads." maxlog=1
end
return nothing
end

_tryparse_env_int(key) = tryparse(Int, get(ENV, key, ""))

function _set_num_threads(::Nothing; _blas = guess_vendor())
n = something(
_tryparse_env_int("OPENBLAS_NUM_THREADS"),
_tryparse_env_int("OMP_NUM_THREADS"),
max(1, Sys.CPU_THREADS ÷ 2),
)
_set_num_threads(n; _blas)
end

"""
get_num_threads()

Get the number of threads the BLAS library is using.

On exotic variants of `BLAS` this function can fail, which is indicated by returning `nothing`.

!!! compat "Julia 1.6"
`get_num_threads` requires at least Julia 1.6.
"""
get_num_threads(;_blas=guess_vendor())::Union{Int, Nothing} = _get_num_threads()
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason why _blas isn't passed to _get_num_threads?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for reporting, I will fix it!


function _get_num_threads(; _blas = guess_vendor())::Union{Int, Nothing}
if _blas === :openblas || _blas === :openblas64
return Int(ccall((@blasfunc(openblas_get_num_threads), libblas), Cint, ()))
elseif _blas === :mkl
return Int(ccall((:mkl_get_max_threads, libblas), Cint, ()))
elseif _blas === :osxblas
key = "VECLIB_MAXIMUM_THREADS"
nt = _tryparse_env_int(key)
if nt === nothing
@warn "Failed to read environment variable $key" maxlog=1
else
return nt
end
else
@assert _blas === :unknown
end
@warn "Could not get number of BLAS threads. Returning `nothing` instead." maxlog=1
return nothing
end

Expand Down
27 changes: 27 additions & 0 deletions stdlib/LinearAlgebra/test/blas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -553,4 +553,31 @@ Base.stride(A::WrappedArray, i::Int) = stride(A.A, i)
end
end

@testset "get_set_num_threads" begin
default = BLAS.get_num_threads()
@test default isa Int
@test default > 0
BLAS.set_num_threads(1)
@test BLAS.get_num_threads() === 1
BLAS.set_num_threads(default)
@test BLAS.get_num_threads() === default

jw3126 marked this conversation as resolved.
Show resolved Hide resolved
@test_logs (:warn,) match_mode=:any BLAS._set_num_threads(1, _blas=:unknown)
if BLAS.guess_vendor() !== :osxblas
# test osxblas which is not covered by CI
withenv("VECLIB_MAXIMUM_THREADS" => nothing) do
@test @test_logs(
(:warn,),
(:warn,),
match_mode=:any,
BLAS._get_num_threads(_blas=:osxblas),
) === nothing
@test_logs BLAS._set_num_threads(1, _blas=:osxblas)
@test @test_logs(BLAS._get_num_threads(_blas=:osxblas)) === 1
@test_logs BLAS._set_num_threads(2, _blas=:osxblas)
@test @test_logs(BLAS._get_num_threads(_blas=:osxblas)) === 2
end
end
end

end # module TestBLAS