# Inference problem with @spawn

**URL:** <https://discourse.julialang.org/t/inference-problem-with-spawn/73379>\
**Category:** Performance\
**Created:** [December 20, 2021, 4:20pm UTC](https://discourse.julialang.org/t/inference-problem-with-spawn/73379 "2021-12-20T16:20:00Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [December 20, 2021, 4:20pm UTC](https://discourse.julialang.org/t/inference-problem-with-spawn/73379/1 "2021-12-20T16:20:00Z")

</div>

This is a toy example of the problem. I want to sum a bunch of number is parallel, using a lock to reduce.

Disconsidering the fact that the lock here is of course detrimental to performance (the actual use case is different), is this the correct way to use one? Why is the output type not inferred? Anything I can do about it?

```julia
julia> function test(x)
           s = zero(eltype(x))
           lk = ReentrantLock()
           @sync for i in 1:1000
               Threads.@spawn begin
                   spart = sum(@view(x[8*(i-1)+1:8*i]))
                   lock(lk) do
                       s += spart
                   end
               end
           end
           return s
       end
test (generic function with 1 method)

julia> @code_warntype test(ones(Int,8000))
MethodInstance for test(::Vector{Int64})
  from test(x) in Main at REPL[11]:1
Arguments
  #self#::Core.Const(test)
  x::Vector{Int64}
Locals
  lk::ReentrantLock
  s@_4::Core.Box
  @_5::Union{Nothing, Tuple{Int64, Int64}}
  v::Nothing
  sync#41::Channel{Any}
  i::Int64
  #15::var"#15#17"{Vector{Int64}, ReentrantLock, Int64}
  task::Task
  s@_11::Union{}
Body::Any
1 ─ (s@_4 = Core.Box())
│ %2 = Main.eltype(x)::Core.Const(Int64)
│ %3 = Main.zero(%2)::Core.Const(0)

```

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [December 20, 2021, 4:23pm UTC](https://discourse.julialang.org/t/inference-problem-with-spawn/73379/2 "2021-12-20T16:23:37Z")

</div>

From what I understand [https://github.com/JuliaLang/julia/pull/41449](https://github.com/JuliaLang/julia/pull/41449) is a prerequisite for inferring `@spawn` (which is not in 1.7). And even with this, further work is needed to make it happen.

---

<div class="post-metadata">

**Author:** ![simeonschaub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simeonschaub/32/216566_2.png) [@simeonschaub](https://discourse.julialang.org/u/simeonschaub)\
**Post date:** [December 20, 2021, 5:06pm UTC](https://discourse.julialang.org/t/inference-problem-with-spawn/73379/3 "2021-12-20T17:06:51Z")

</div>

In this case however I believe the problem is that `Threads.@spawn` as well as the `lock(ok) do ...` pattern create a closure. Since you assign to `s` from both outside as well as inside the closure, you are running into

[https://github.com/JuliaLang/julia/issues/15276](https://github.com/JuliaLang/julia/issues/15276)

Use an `Atomic{T}` for `s` instead and increase the counter using `atomic_add!`, which should also typically be a lot more efficient anyways.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [December 20, 2021, 5:15pm UTC](https://discourse.julialang.org/t/inference-problem-with-spawn/73379/4 "2021-12-20T17:15:10Z")

</div>

> [@simeonschaub](#):
>
> Use an `Atomic{T}` for `s` instead and increase the counter using `atomic_add!` , which should also typically be a lot more efficient anyways.

In the real case `s` is a complicated structure, as I understand that would not apply.

But the problem is independent of the lock (although of course there the result is wrong):

```julia
julia> function test(x)
           s = zero(eltype(x))
           @sync for i in 1:1000
               Threads.@spawn begin
                   spart = sum(@view(x[8*(i-1)+1:8*i]))
                   s += spart
               end
           end
           return s
       end
test (generic function with 1 method)

julia> @code_warntype test(ones(Int,8000))
MethodInstance for test(::Vector{Int64})
  from test(x) in Main at REPL[13]:1
Arguments
  #self#::Core.Const(test)
  x::Vector{Int64}
Locals
  s@_3::Core.Box
  @_4::Union{Nothing, Tuple{Int64, Int64}}
  v::Nothing
  sync#41::Channel{Any}
  i::Int64
  #3::var"#3#4"{Vector{Int64}, Int64}
  task::Task
  s@_10::Union{}
Body::Any
1 ─ (s@_3 = Core.Box())

```

---

<div class="post-metadata">

**Author:** ![simeonschaub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simeonschaub/32/216566_2.png) [@simeonschaub](https://discourse.julialang.org/u/simeonschaub)\
**Post date:** [December 20, 2021, 5:31pm UTC](https://discourse.julialang.org/t/inference-problem-with-spawn/73379/5 "2021-12-20T17:31:31Z")

</div>

As I explained above, `Threads.@spawn` also creates a closure. For more complicated structures, you might want to have a look at the newly added [per-field atomics](https://docs.julialang.org/en/v1/manual/multi-threading/#man-atomics). If you insist on using the explicit lock pattern here though, you could also just make `s` a `Ref`.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [December 20, 2021, 5:39pm UTC](https://discourse.julialang.org/t/inference-problem-with-spawn/73379/6 "2021-12-20T17:39:00Z")

</div>

> [@simeonschaub](#):
>
> If you insist on using the explicit lock pattern here though, you could also just make `s` a `Ref` .

Uhm… that is an idea, didn’t think about that one. (nothing against the new per-field atomic, just not sure if supporting only 1.7 is a good idea now). I am experimenting with some alternatives for now, thanks for the hint.
