Lightweight tasks, Julia vs Elixir/OTP

You are right, this seems to be the cause of the problem. I extended my last example:

function machin_series(n::Int; handover=false)
    qpi = 0.0
    for i in 1:n
        qpi += (-1)^(i+1)/(2i-1)
        handover && yield()     # we yield here!
    end
    qpi*4
end

function startonthread(id::Int, f::F) where {F<:Function}
    t = Task(nothing)
    @threads for i in 1:nthreads()
        if i == id
            t = @async f()
        end
    end
    fetch(t)
end

This gives the following results:

julia> @btime startonthread(1, ()->machin_series(10_000))
  491.870 μs (43 allocations: 4.20 KiB)
3.1414926535900345

julia> @btime startonthread(2, ()->machin_series(10_000))
  511.001 μs (45 allocations: 4.23 KiB)
3.1414926535900345

julia> @btime startonthread(1, ()->machin_series(10_000, handover=true))
  147.722 ms (10044 allocations: 160.47 KiB)  !!!!!!!!!
3.1414926535900345

julia> @btime startonthread(2, ()->machin_series(10_000, handover=true))
  2.477 ms (10045 allocations: 160.48 KiB)
3.1414926535900345

julia> @btime startonthread(3, ()->machin_series(10_000, handover=true))
  2.499 ms (10045 allocations: 160.48 KiB)
3.1414926535900345

If I yield() inside my loop, things take longer on thread 1 than on other threads! Applications involving tasks, @async, yield and friends run faster on threads other than 1.

Now in concluding this journey, I provide the following function for executing functions on specified threads:

function onthread(f::F, id::Int) where {F<:Function}
    t = Task(nothing)
    @assert id in 1:nthreads() "thread $id not available!"
    @threads for i in 1:nthreads()
        if i == id
            t = @async f()
        end
    end
    fetch(t)
end

Anyone having single threaded applications involving tasks can speed things up with it. With the above (contrived) example you can do

julia> @btime machin_series(10_000, handover=true)
  158.559 ms (10000 allocations: 156.25 KiB)
3.1414926535900345

julia> @btime onthread(2) do; machin_series(10_000, handover=true); end
  2.467 ms (10045 allocations: 160.48 KiB)
3.1414926535900345

More realistically I still get dramatic speedups of my simulation benchmarks with this:

date channel [ms] dice [ms] heating [ms] action [μs] comment
2020-02-20 42.431 296.433 59.631 38.156 before
2020-02-27 44.630 36.641 5.091 8.269 dice and heating onthread(2)

Maybe, now you can see why I went after this thing :slight_smile: .

You can just copy/paste onthread from above or get it from my repo by

] add "https://github.com/pbayer/ThreadingExperiments.jl"

julia> using ThreadingExperiments

Its compat is Julia 1.3. Maybe someone of you could test (by copying from this thread) if this thing works as well in Julia 1.2 and below.

2 Likes