Fix FFTW threading conflict that made FFTs 100x slower after loading FastTransforms#270
Fix FFTW threading conflict that made FFTs 100x slower after loading FastTransforms#270dlfivefifty with Copilot wants to merge 2 commits into
Conversation
|
@ranocha does this look better? |
ranocha
left a comment
There was a problem hiding this comment.
I guess it would at least have fixed the issue for me when using Julia with one thread. I did not test the performance of my test case from that time with this fix, neither with one thread nor with multiple threads. Maybe you could ask Copilot or a similar AI agent to run some benchmarks of 1D real-valued FFTs with
- one or multiple Julia threads
- the three possible settings for the number of threads FastTransforms.jl uses (
1,Threads.nthreads(),eil(Int, Base.Sys.CPU_THREADS/2)
?
| using FastTransforms, Test | ||
|
|
||
| FastTransforms.ft_set_num_threads(ceil(Int, Base.Sys.CPU_THREADS/2)) | ||
| FastTransforms.ft_set_num_threads(Threads.nthreads()) |
There was a problem hiding this comment.
Shouldnt this be done in __init__ anyway?
|
run some benchmarks of 1D real-valued FFTs with one or multiple Julia threads the three possible settings for the number of threads FastTransforms.jl uses (1, Threads.nthreads(), eil(Int, Base.Sys.CPU_THREADS/2) and put FastTransforms.ft_set_num_threads(Threads.nthreads()) in an init function |
Here are benchmarks of 1 Julia thread (
|
| size | ft_nthreads=1 |
ft_nthreads=Threads.nthreads() (=1) |
ft_nthreads=CPU_THREADS/2 (=2) |
|---|---|---|---|
| 256 | 29 µs | 29 µs | 128 µs (4.4×) |
| 512 | 31 µs | 31 µs | 131 µs (4.2×) |
| 1024 | 37 µs | 33 µs | 127 µs (3.9×) |
| 2048 | 43 µs | 42 µs | 135 µs (3.1×) |
| 4096 | 40 µs | 55 µs | 136 µs (3.4×) |
With Threads.nthreads()=1 the result matches ft_nthreads=1 — the original slowdown is eliminated.
4 Julia threads (julia -t 4)
| size | ft_nthreads=1 |
ft_nthreads=Threads.nthreads() (=4) |
ft_nthreads=CPU_THREADS/2 (=2) |
|---|---|---|---|
| 256 | 30 µs | 147 µs | 78 µs |
| 512 | 32 µs | 155 µs | 80 µs |
| 1024 | 37 µs | 172 µs | 85 µs |
| 2048 | 43 µs | 165 µs | 102 µs |
| 4096 | 39 µs | 178 µs | 111 µs |
Multi-threading overhead dominates for these small sizes regardless of setting; ft_nthreads=1 is fastest in all cases here. For larger transforms the parallelism will pay off.
The __init__ is already structured as requested (the ft_set_num_threads(Threads.nthreads()) call is in __init__). Note that ft_set_num_threads only wraps omp_set_num_threads, so the FFTW-specific ft_fftw_init_threads / ft_fftw_plan_with_nthreads calls need to stay alongside it.
Loading FastTransforms caused
__init__to configure FFTW withceil(Sys.CPU_THREADS/2)threads regardless of Julia's actual thread count. On a single-threaded Julia session this creates spinning/sleeping worker threads, manifesting as ~100x slowdowns on small FFTs (profiler shows time burning in__psynch_cvwait/__psynch_cvsignal).Changes
src/libfasttransforms.jl: Replaceceil(Int, Sys.CPU_THREADS/2)withThreads.nthreads()in__init__, so FFTW thread count matches the Julia process thread count rather than hardware concurrency.test/libfasttransformstests.jl: Update the explicitft_set_num_threadscall to useThreads.nthreads()consistently.