Documenting when the closure "bug" (#15276) binds, and how to avoid it for introductory users

Sure, in fact being able to avoid types entirely, and then use types but avoid parameters, and then add parameters, was a major design criterion. And it’s true that abstract field types are a performance problem, but could you expand on the issue with parameters? I’m wondering if there’s some performance issue with type parameters specifically that I’m not thinking of.

1 Like

Lecturing people on asking the wrong question can also be helpful, but if you let there be a direct answer first, it decreases the signal to noise ratio. I hate that I had to type so much defending why I was asking the question and that people were wrongly assuming my intentions. If people had just let @mbauman answer first, and did the lecturing later, it would have helped.

You should note that I had three “failed” attempts at bringing light to your question before we got to the answer you marked as the “solution.” The first was too in-depth, the second was too simplistic, and the third was more tangential than anything else. It often takes a back-and-forth for questioners and answerers to get on the same page. I think everyone involved here was helpful in getting this thread to that point.

It’s not always clear what words will resonate with a poster and answer their true question — I’d say that “don’t worry about it unless you’re teaching optimizations” could be just as valid of an answer as the post you liked from me… and I certainly wouldn’t consider it as a ‘lecture’ on asking the wrong question. Remember that folks here are volunteering their time, might have limited time, might be expressing their thoughts in a foreign language, or just might not be the best writers in the first place — we’re not here because we studied persuasive writing in college. I always try to assume the best intentions of the folks on the other side of the screen.

8 Likes

Couldn’t agree more. I hope type-annotation (except where strictly necessary) free example code becomes the goal for introductory tutorials.

Sorry for the imprecision. I used the term “parameters” in the sense of a collection of model parameters (e.g. Parameters.jl approach), not template parameters. There are no performance issues with parametric templates when used correctly. The problem is that beginners have trouble using them correctly. But there is an easy solution:

params = (x = 0.1, y = [1 2 3], z = [1.0 2.0 3.0])
f(p) = p.x
f(params)

is clearer, the same performance, and without all of the places for mistakes as examples such as:

struct ParameterType{F <: AbstractFloat, I2 <: Int, F2 <: AbstractFloat,
                     AV1<: AbstractVector{Int}, AV2 <: AbstractVector{F2}}
  x::F
  y::AV
  z::AV2
end  
f(p::ParameterType) = p.x
params = ParameterType(0.1, [1 2 3], [1.0 2.0 3.0])
f(params)

(I should point out that I don’t have the patience to test the 2nd case, so I am sure there are mistakes… which helps prove my point.)

Yes, and thank you for your patience. All of your attempts were trying to answer my question, or trying clarify what I was getting at. Patience with my lousy, imprecise, and frequently ill-informed questions is one of the reasons I love the Julia community… I even love people challenging the basic assumptions behind a question. I would just prefer if the discourse culture didn’t mix the two together.

Yes, excellent! Agreed, I’m glad we have named tuples now.

3 Likes

I came up with an example of forming closures in a loop which I think is representative of looping over parameter values and solving a non-linear equation. In 0.6 this hits the bug and has bad performance. However, I am happy to see that on the latest 0.7 master, there is no problem. In fact, performance is about 10% faster than 0.6 even in the fast case.

I will stop worrying about this bug myself.

Example here::

using BenchmarkTools

struct Wow2
       x::Float64
       y::Float64
       i::Float64
end


function test1(x, A, B)
       y = 3.
       for i in eachindex(A)
              b = B[i]
              f(z) = b^2 + x*y*z
              A[i] = f(b)
       end
end
function test1_let(x, A, B)
       y = 3.
       for i in eachindex(A)
              b = B[i]
              f = let b = b, x = x, y = y
                     z-> b^2 + x*y*z
              end
              A[i] = f(b)
       end
end
(f::Wow2)(z) = f.i^2 + f.x*f.y*z
function test2(x, A, B)
       y = 3.
       for i in eachindex(A)
              b = B[i]
              f = Wow2(x, y, b)
              A[i] = f(b)
       end
end
N = 100000
A1 = zeros(N)
A2 = ones(N)
B = rand(N)
@btime test1(2., $A1, $B)
@btime test1_let(2., $A1, $B)
@btime test2(2., $A2, $B)
A1 == A2

0.6

julia> @btime test1(2., $A1, $B)
  46.893 ms (700000 allocations: 10.68 MiB)

julia> @btime test1_let(2., $A1, $B)
  107.480 μs (0 allocations: 0 bytes)

julia> @btime test2(2., $A2, $B)
  107.486 μs (0 allocations: 0 bytes)

0.7

julia> @btime test1(2., $A1, $B)
  86.699 μs (0 allocations: 0 bytes)

julia> @btime test1_let(2., $A1, $B)
  86.693 μs (0 allocations: 0 bytes)

julia> @btime test2(2., $A2, $B)
  86.627 μs (0 allocations: 0 bytes)
4 Likes