I have found some unexpected behavior when trying to capture the result of Threads.nthreads(). The use-case is the following:
module MyModule
# some algorithms that may or may not use multithreading
# Default settings controlling the algorithms
module Defaults
const ntasks = Threads.nthreads()
end
end
Interestingly, with this design, I get the following output, even when starting julia with multiple threads:
julia> Threads.nthreads()
8
julia> using MyModule; MyModule.Defaults.ntasks
1
It seems like the const value is computed before the Threads.nthreads() takes on its value, so I must be missing something here. Is this expected behavior? Does this mean that if I want to have access to nthreads, I would have to either use a function call, or some form of __init__ procedure?
It seems that your module was first precompiled in a single threaded context. The codes inside the module will not be reevaluated even if you restart, hence ntasks will always equal 1 no matter how many threads you restart julia with.
You can use __init__ to execute a code whenever the module is loaded.
For more info, I believe @oxinabox 's post will be helpful.
Maybe you want to elaborate what you want to ultimately achieve. This sounds like a potential XY problem.
My understanding is that you want to “cache” the value of Threads.nthreads(), but that’s such a quick function that caching it is probably not worth the effort (and the risk of doing it wrong):
julia> @benchmark Threads.nthreads()
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
Range (min … max): 1.742 ns … 9.479 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 1.749 ns ┊ GC (median): 0.00%
Time (mean ± σ): 1.756 ns ± 0.115 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
▅█
▅██▄▂▂▁▂▂▁▂▁▁▂▂▁▁▂▁▁▁▂▂▂▁▁▁▂▂▁▁▁▁▁▁▁▂▂▂▂▂▁▁▁▁▁▁▁▂▁▂▂▁▂▁▂▂ ▂
1.74 ns Histogram: frequency by time 1.93 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
Also, keep in mind that the number of Julia threads currently can’t be changed after a Julia session is started, but that restriction may be lifted at some point in the future, which makes caching the value of nthreads() ill-advised.
Thanks for that link to @oxinabox 's post, that’s definitely the information I was looking for!
@giordano , in all honesty, I really was mostly looking for information to understand the behavior, not so much a solution to the specific problem. My apologies if my question did not fully reflect this. For completeness, the specific pattern I had involves a structure like this:
module Defaults
using OhMyThreads
const maxiter = 100
const tol = 1e-6
const scheduler = Threads.nthreads() == 1 ? SerialScheduler() : DynamicScheduler()
# and more default settings
end
function myalgorithm(args...; tol=Defaults.tol, maxiter=Defaults.maxiter, ntasks=Defaults.ntasks)
# implementation
end
Specifically, I have some defaults for a number of algorithms that I like keeping in a centralized spot of the repository, ie a module Defaults. Naively putting the call to Threads.nthreads() doesn’t work due to the explanations you gave me, and for consistency it’s less nice (although completely okay) to have some defaults being variables, and others being ones that you have to call.
It really has not a lot to do with speed, more than code aesthetics, convenience and consistency, and mostly with me understanding why something works or not. Thanks though!