Skip to content

Add get_num_threads#171

Merged
stevengj merged 16 commits intoJuliaMath:masterfrom
galenlynch:add_get_num_threads
Mar 14, 2022
Merged

Add get_num_threads#171
stevengj merged 16 commits intoJuliaMath:masterfrom
galenlynch:add_get_num_threads

Conversation

@galenlynch
Copy link
Copy Markdown
Contributor

@galenlynch galenlynch commented Dec 20, 2020

This commit adds get_num_threads, which returns the number of threads used by the planner, and is the complement to set_num_threads. This simply wraps the function fftw_planner_nthreads, which was newly added to fftw in version 3.3.9.

This PR will not work until JuliaPackaging/Yggdrasil#2291 is merged, and the resulting artifact is used for testing.

Closes #117

This commit adds `get_num_threads`, which returns the number of threads used by
the planner, and is the complement to `set_num_threads`. This simply wraps the
function `fftw_planner_nthreads`, which was
[newly added to fftw in version 3.3.9](https://github.com/FFTW/fftw3/blob/34082eb5d6ed7dc9436915df69f376c06fc39762/NEWS#L3).
@galenlynch
Copy link
Copy Markdown
Contributor Author

This is now working locally for me, and tests are passing, when used with FFTW_jll v3.3.9+7.

@stevengj
Copy link
Copy Markdown
Member

stevengj commented Dec 21, 2020

Can you bump the version requirement on FFTW_jll in Project.toml?

@galenlynch
Copy link
Copy Markdown
Contributor Author

galenlynch commented Dec 21, 2020

Yeah I wasn't sure what to do about that... I don't think you can specify a build number in Project.toml. If I just change the compat section so that FFTW_jll's version is "3.3.9+7" I get the following error when trying to use FFTW:

ERROR: Could not parse compatibility version for dependency `FFTW_jll`

As long as people update their packages this should work since they will get the most recent build of FFTW_jll, but I don't know how to make sure they get v3.3.9+7 (or later).

I can update the version requirement for FFTW_jll to be 3.3.9, but that won't always work if people are using an older FFTW_jll build. Would that suffice?

`get_num_threads` requires FFTW_jll v3.3.9+7, but it doesn't seem possible to
specify a particular build in the compat section of Project.toml files. However,
this should work in most cases, as the most recent build of `FFTW_jll` should be
downloaded upon updating.
@giordano
Copy link
Copy Markdown
Member

giordano commented Dec 21, 2020

I don't think you can specify a build number in Project.toml.

That's another reason why we want to change the versioning system of JLL package (and the reason why I was waiting to merge the PR, I knew we'd have had this situation)

@galenlynch
Copy link
Copy Markdown
Contributor Author

I imagine that not being able to specify the build number will not be a problem for people, unless they have pinned or dev'd FFTW_jll.

In order to make sure that it works, it seems like you would need to create a new FFTW_jll version v3.3.10 that corresponds to fftw3 v3.3.9, because FFTW_jll v.3.3.9 has been wrapping a pre-release version of fftw v3.3.9 for a while (likely for a good reason), meaning that there is currently no way to specify the released fftw 3.3.9 artifact with FFTW_jll's version.

I'm still not clear on how package maintainers will be able to specify which artifact version they want if the _jll package version number is not the same as the underlying artifact, even though I understand why it is necessary for them to not be the same. Will there be a lookup table in the README of each _jll github project page? Or will there be some other programmatic way to do it, which might even have Pkg support?

@giordano
Copy link
Copy Markdown
Member

giordano commented Dec 21, 2020

I imagine that not being able to specify the build number will not be a problem for people, unless they have pinned or dev'd FFTW_jll.

There are weird situations where users may end up updating FFTW but not FFTW_jll. Not really common, but not totally impossible.

I'm still not clear on how package maintainers will be able to specify which artifact version they want if the _jll package version number is not the same as the underlying artifact, even though I understand why it is necessary for them to not be the same. Will there be a lookup table in the README of each _jll github project page? Or will there be some other programmatic way to do it, which might even have Pkg support?

We haven't worked this out yet because it isn't a trivial problem. If you have some ideas please share in Yggdrasil.

@galenlynch
Copy link
Copy Markdown
Contributor Author

Not really common, but not totally impossible.

Exactly, it seems like a pretty unlikely problem.

I think that this PR should be merged before there is both consensus about, and implementation of, and improved version system in Yggdrasil—which may take quite some time. The change here simply provides access to a function in the most recent version of fftw. I have updated the Project.toml for this package so that it requires that version of fftw (3.3.9), which should just work for the vast majority of users, and indeed worked for CI here. The potential downside of adding this function is that due to their local Pkg state, it's possible that a user will have a fftw library that reflects the state of fftw around Sep 17 2019, instead of the released 3.3.9 version, causing this new function to error. However, this Pkg state seems unlikely, even though it is possible. The fix for any affected users would be to simply run pkg> update, and possibly unpin their FFTW_jll, if they had it pinned.

Furthermore, it seems like the reason that FFTW_jll was shipping a pre-released version of fftw was to provide access to fftw_threads_set_callback, enabling mutlithreaded FFTW plans using Julia's partr threads. I see substantial performance improvements in DSP.jl's convolutions when using fftw's multithreaded transformations, but different sized convolutions seem to perform best with different numbers of threads (often one is best). While packages can change the number of threads being used by fftw with set_num_threads, doing so currently changes the global state of Julia since there is no way to see what that number was beforehand. This new function, get_num_threads, allows package authors to programatically change the number of fftw threads without changing global sate, and in a way would unlock the benefits of the (great) change that @stevengj made to enable fftw to use Julia's threads, even though that change is causing some consternation with package versions at the moment.

Comment thread src/fft.jl Outdated
@galenlynch
Copy link
Copy Markdown
Contributor Author

galenlynch commented Dec 25, 2020

I added the vendor check.

It occurs to me that the way I would want to use this would benefit from support in this package. I would want to acquire the fftwlock, change the number of threads, make some plans, restore the original number of threads, and finally release the fftwlock. To minimize version numbers I could add it to this PR, if you approve.

The function would look something like:

@exclusive function set_num_threads(f, nthreads::Integer)
    orig_nthreads = get_num_threads()
    try
        ccall((:fftw_plan_with_nthreads,libfftw3), Cvoid, (Int32,), nthreads)
        ccall((:fftwf_plan_with_nthreads,libfftw3f), Cvoid, (Int32,), nthreads)
        f()
    finally
        ccall((:fftw_plan_with_nthreads,libfftw3), Cvoid, (Int32,), orig_nthreads)
        ccall((:fftwf_plan_with_nthreads,libfftw3f), Cvoid, (Int32,), orig_nthreads)
    end
end

and would enable planning like:

plan = FFTW.set_num_threads(4) do
    plan_rfft(...)
end

The function argument would have to be careful to avoid a context switch, e.g. no IO. However, it seems like you would want to hold the lock while making plans to ensure that the plans were made with the intended number of threads.

@stevengj
Copy link
Copy Markdown
Member

The function argument would have to be careful to avoid a context switch, e.g. no IO.

Why?

@galenlynch
Copy link
Copy Markdown
Contributor Author

galenlynch commented Dec 27, 2020 via email

@stevengj
Copy link
Copy Markdown
Member

Yes, adding that set_num_threads method seems reasonable.

(You wouldn't need to ccall though — just call set_num_threads(::Integer).)

@stevengj
Copy link
Copy Markdown
Member

Or we could add an nthreads keyword argument to the plan_fft functions.

@galenlynch
Copy link
Copy Markdown
Contributor Author

You wouldn't need to ccall though — just call set_num_threads(::Integer)

Wouldn't set_num_threads(::Integer) try to acquire the lock again?

Or we could add an nthreads keyword argument to the plan_fft functions.

That also sounds great

Comment thread src/fft.jl Outdated
Additionally, separate previous `set_num_threads` method into a base function,
`_set_num_threads`, that wraps the `ccalls`, and `set_num_threads`, which will
acquire the `fftwlock`.
While MKL's FFTW does not provide access to the number of threads available to
the planner, this can be simulated by caching the value last passed to
`set_num_threads` and returning it with `get_num_threads` if
`fftw_vendor == :mkl`.
@codecov
Copy link
Copy Markdown

codecov bot commented Dec 28, 2020

Codecov Report

Merging #171 (1af16ec) into master (683a6e8) will decrease coverage by 2.16%.
The diff coverage is 42.30%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #171      +/-   ##
==========================================
- Coverage   63.24%   61.07%   -2.17%     
==========================================
  Files           5        5              
  Lines         438      483      +45     
==========================================
+ Hits          277      295      +18     
- Misses        161      188      +27     
Impacted Files Coverage Δ
src/dct.jl 80.85% <ø> (ø)
src/providers.jl 37.03% <ø> (ø)
src/fft.jl 61.09% <42.30%> (-2.81%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 683a6e8...1af16ec. Read the comment docs.

Comment thread src/FFTW.jl Outdated
Comment thread src/fft.jl Outdated
…s.nthreads

Since FFTW uses `Base.Threads`, and `nthreads` is a function defined in
`Base.Threads`, then the function argument `nthreads` shadows a function already
in the namespace of every function. While there is no inherent issue with this,
it can make debugging this code more confusing.
As suggested by @stevengj, I have add a `num_threads` keyword to the `plan_...`
functions. My approach here is fairly naive, and adds a bunch of redundant
boiler plate code to every `plan_` function.
@galenlynch
Copy link
Copy Markdown
Contributor Author

I have implemented the suggestions in your code reviews, as well as your suggestion to add a num_threads keyword to the plan_... functions. My approach to adding the num_threads keyword argument here is pretty naive, but I couldn't think of a better way of adding the keyword argument to all of these functions. I haven't yet added tests for this new keyword argument, since I'm not sure this is the way you'd want to proceed. If so, I'd be happy to try to add some tests.

@galenlynch
Copy link
Copy Markdown
Contributor Author

Gentle bump on this. Not sure what's left to be done but I would love to see this merged.

Comment thread src/dct.jl

"""
plan_dct!(A [, dims [, flags [, timelimit]]])
plan_dct!(A [, dims [, flags [, timelimit [, num_threads]]]])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why aren't these keyword arguments?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was just following the existing style for the keyword arguments flags and timelimit. Happy to change the documentation style (and probably flags and timelimit too?) if you'd prefer.

Comment thread src/FFTW.jl Outdated
@tknopp
Copy link
Copy Markdown
Member

tknopp commented Jan 19, 2022

bump. The depending package NFFT.jl is multi-threaded and it would be nice to chose the same number of threads all operations without changing global state.

@IanButterworth
Copy link
Copy Markdown
Contributor

Another friendly bump 🙏.
This would be nice to have.

@giordano
Copy link
Copy Markdown
Member

I merged master onto this branch, I hope @galenlynch won't mind 🙂

@stevengj stevengj merged commit 17bc81a into JuliaMath:master Mar 14, 2022
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.

get version of FFTW.set_num_threads()?

5 participants