Toggle Threads.@threads with a variable

Hello

I am sure the elegant answer is in the documentation, but I could not find it.

I use threads but sometimes want to execute same loop without threads. Is there a way to tell Julia not to use Threads.@threads if some variable, like DoThreads==false, without repeating the for loop with and without Threads.@threads

all the best, Jack

As far as I know, there is not.

I have used and have seen the use of

    if parallel && nthreads() > 1

In more than one package.

I sometime use a clever pattern which has been originally suggested here:

module MyModule

using Base.Threads

use_threads() = true

macro maybe_threads(code)
    return esc(:(
        if $(@__MODULE__).use_threads()
            @threads($code)
        else
            $code
        end
    ))
end

function computation()
    @maybe_threads for i in 1:10
        @show i, threadid()
    end
end

end

which gives

julia> MyModule.computation()
(i, threadid()) = (3, 2)
(i, threadid()) = (4, 2)
(i, threadid()) = (1, 1)
(i, threadid()) = (2, 1)
(i, threadid()) = (10, 6)
(i, threadid()) = (7, 4)
(i, threadid()) = (8, 4)
(i, threadid()) = (9, 5)
(i, threadid()) = (5, 3)
(i, threadid()) = (6, 3)

julia> MyModule.use_threads() = false

julia> MyModule.computation()
(i, threadid()) = (1, 1)
(i, threadid()) = (2, 1)
(i, threadid()) = (3, 1)
(i, threadid()) = (4, 1)
(i, threadid()) = (5, 1)
(i, threadid()) = (6, 1)
(i, threadid()) = (7, 1)
(i, threadid()) = (8, 1)
(i, threadid()) = (9, 1)
(i, threadid()) = (10, 1)

julia> MyModule.use_threads() = true

julia> MyModule.computation()
(i, threadid()) = (5, 3)
(i, threadid()) = (9, 5)
(i, threadid()) = (3, 2)
(i, threadid()) = (4, 2)
(i, threadid()) = (10, 6)
(i, threadid()) = (6, 3)
(i, threadid()) = (1, 1)
(i, threadid()) = (2, 1)
(i, threadid()) = (7, 4)
(i, threadid()) = (8, 4)
7 Likes

Another possibility would be to use FLoops.jl
By doing

@floop ex for ...

The ex variable can be used to control the parallel execution. By setting ex = ThreadedEx() it uses multithreading, setting it to ex = SequentialEx() no threads are used.

2 Likes

Fantastic, thanks for all the suggestions. I knew Julia could do it! All look like they will solve my problem, and I will give all a spin. Floops.jl looks very nice! So many excellent Julia packages.

thanks again, Jack.

1 Like

For a runtime flag, I recently rolled my own solution at QuantumControlBase.jl/conditionalthreads.jl at master · JuliaQuantumControl/QuantumControlBase.jl · GitHub, as discussed in Optional macro invocation - #21 by goerz

For non-library uses, “toggling” Threads.@threads with a macro that generates two versions is a decent solution.

For library authors, I strongly recommend specifying the size of the base case rather than the number of tasks used. In JuliaFolds packages such as FLoops.jl, it is called the basesize. The idea is to determine the “size” of the sub-problem such that computation of it takes roughly at least (say) a few hundred of microseconds. Here, basesize is of arbitrary unit. For example, for linear-time computation (e.g., map), it can be the number of elements. This way, you’d only spawn roughly ntasks = size_of_the_whole_problem / basesize tasks. If the size of the whole problem is not big enough, it’d only consume ntasks CPUs. This is a very important property for composable parallel programs since other components in your program may want to use the CPUs. Furthermore, since a program using this strategy will automatically degenerate to a single-thread program when the problem size is small enough (i.e., it is not worth spawning tasks), you don’t need to implement the extra mechanism for toggling the run-time option.

1 Like

Thanks goerz,

Your solution worked brilliantly. @floop didn’t work since it complained about local variables and race conditions. I am realising from your answer and tktf’s suggestion, I better learn macro programming. It should be super helpful.

Thanks again everybody, it is due to the excellence of Julia and the friendliness of the community that those of us who are python and R refugees find is so easy to move over.

best, jack

There’s no need to learn macro meta programming to use Julia. In fact, it is a common pitfall that people write non-composable programs with macros (and @generated functions) when more straightforward abstraction with plain functions and types is possible.

@floop macro is just a short hand for calling Folds.reduce. As mentioned in the “Manual reductions” section of my tutorial A quick introduction to data parallelism in Julia, you can manually call Folds.reduce without using any macros.

2 Likes