# Why is this closure over keyword arguments causing such a dramatic slowdown?

**URL:** <https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308>\
**Category:** Performance\
**Tags:** question, kwargs, closure\
**Created:** [March 21, 2020, 6:16pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308 "2020-03-21T18:16:13Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![anon32405968](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@anon32405968](https://discourse.julialang.org/u/anon32405968)\
**Post date:** [March 21, 2020, 6:16pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/1 "2020-03-21T18:16:13Z")

</div>

I’m writing a function that allows a user to freeze some variables of a function (needs to be generic with respect to the names and number of keyword arguments). Design-wise, I now have exactly what I need and the code is nice and neat, **but** I am really surprised at the overhead I am seeing.

_ **Does anyone know why I am hitting an 8x slowdown and if it can be optimised?** _

Minimum Working Example below:

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

L1(arg1; kwargs...) = L0(arg1; firstkwargs..., kwargs...)

@btime L0(0; $allkwargs...)
@btime L1(0; $lastkwargs...)

```

Which returns:

```julia
1.205 ns (0 allocations: 0 bytes)
87.518 ns (5 allocations: 144 bytes)

```

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 21, 2020, 6:21pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/2 "2020-03-21T18:21:19Z")

</div>

in this line

```julia
L1(arg1; kwargs...) = L0(arg1; firstkwargs..., kwargs...)

```

`firstkwargs` is a global variable. Number one performance tip: avoid non-constant global varaibles [Performance Tips · The Julia Language](https://docs.julialang.org/en/v1/manual/performance-tips/index.html)

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

const allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
const firstkwargs = (kwarg1=1, kwarg2=2)
const lastkwargs = (kwarg3=3, kwarg4=4)

L1(arg1; kwargs...) = L0(arg1; firstkwargs..., kwargs...)

@btime L0(0; $allkwargs...) # 1.422 ns (0 allocations: 0 bytes)
@btime L1(0; $lastkwargs...) # 1.421 ns (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

**Author:** ![anon32405968](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@anon32405968](https://discourse.julialang.org/u/anon32405968)\
**Post date:** [March 21, 2020, 6:34pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/3 "2020-03-21T18:34:52Z")

</div>

@Mason Thanks that makes sense, I was actually doing the ‘freezing’ in a function so I thought I had avoided the global scope but it appears not. Here is a slightly embellished minimum working example:

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

function freeze_params(func_to_freeze, params_to_freeze)
    return (arg1; kwargs...) -> func_to_freeze(arg1; kwargs..., params_to_freeze...)
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
const firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

L1(arg1; kwargs...) = freeze_params(L0, firstkwargs)

@btime L0(0; $allkwargs...)
@btime L1(0; $lastkwargs...)

```

And indeed, exactly as you have said, making firstkwargs `const` has solved the issue. I also tested just inserting the literal value of `firstkwargs` into the `freeze_params` function call and that has a similar effect.

Without `const`:

```julia
  1.506 ns (0 allocations: 0 bytes)
  242.597 ns (2 allocations: 64 bytes)

```

With `const` before `firstkwargs`:

```julia
  1.506 ns (0 allocations: 0 bytes)
  0.001 ns (0 allocations: 0 bytes)

```

In the above MWE would there be any way to force `freeze_params` to use a local version of `params_to_freeze` and thus to avoid having to force a `const` prefix in the code above?

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 21, 2020, 6:43pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/4 "2020-03-21T18:43:27Z")

</div>

You could do this:

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

const L1 = let firstkwargs = firstkwargs
    (arg1; kwargs...) -> L0(arg1; firstkwargs..., kwargs...)
end

@btime L0(0; $allkwargs...) # 1.421 ns (0 allocations: 0 bytes)
@btime L1(0; $lastkwargs...) # 1.420 ns (0 allocations: 0 bytes)

```

here, instead of defining a regular function `L1` I bound the `const L1` to the anonymous function ` (arg1; kwargs...) -> L0(arg1; firstkwargs..., kwargs...)`. The way I used the `let` block there ensures that `firstkwargs` is not a global in the scope of the anonymous function.

This has some downsides though, like this:

```julia
julia> L1
 #14 (generic function with 1 method)

```

and you can’t add methods to `L1` in the regular way you might expect.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 21, 2020, 6:50pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/5 "2020-03-21T18:50:38Z")

</div>

> [@anon32405968](#):
>
> Thanks I think I am with you. Following up on that, what do you mean by this?
> 
> > [@Mason](#):
> >
> > and you can’t add methods to `L1` in the regular way you might expect.
> 
> I tried with one value as a `Float64` for example (which is the most exotic example these functions are likely to face) and it seemed OK.

Are you sure?

```julia-auto
julia> begin
       function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
           arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
       end
       
       allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
       firstkwargs = (kwarg1=1, kwarg2=2)
       lastkwargs = (kwarg3=3, kwarg4=4)
       
       const L1 = let firstkwargs = firstkwargs
           (arg1; kwargs...) -> L0(arg1; firstkwargs..., kwargs...)
       end
       end
#8 (generic function with 1 method)

julia> L1(::Float64) = "hi"
ERROR: cannot define function L1; it already has a value
Stacktrace:
 [1] top-level scope at none:0
 [2] top-level scope at REPL[2]:1

```

---

<div class="post-metadata">

**Author:** ![anon32405968](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@anon32405968](https://discourse.julialang.org/u/anon32405968)\
**Post date:** [March 21, 2020, 6:54pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/6 "2020-03-21T18:54:57Z")

</div>

Thanks I think I am with you. Following up on that, what do you mean by this?

> [@Mason](#):
>
> and you can’t add methods to `L1` in the regular way you might expect.

I tried with one value as a `Float64` for example (which is the most exotic example these functions are likely to face) and it seemed OK.

Regarding changes in `firstkwargs` not propagating, that is no problem at all for the software requirements.

My last challenge then is this, how do I capture your `const` and `let` block logic within the above `freeze_params` function. It seems I was caught out by not considering the passing by sharing in Julia functions. One way I am wondering is if I can just copy the variable within the function so it forces local.

> <https://stackoverflow.com/questions/58150295/how-to-pass-an-object-by-reference-and-value-in-julia>

---

<div class="post-metadata">

**Author:** ![anon32405968](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@anon32405968](https://discourse.julialang.org/u/anon32405968)\
**Post date:** [March 21, 2020, 6:59pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/7 "2020-03-21T18:59:12Z")

</div>

Following up on that with a test, `deepcopy` doesn’t seem to help me here:

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

function freeze_params(func_to_freeze, params_to_freeze)
    local_params_to_freeze = deepcopy(params_to_freeze)
    return (arg1; kwargs...) -> func_to_freeze(arg1; kwargs..., local_params_to_freeze...)
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

L1(arg1; kwargs...) = freeze_params(L0, firstkwargs)

@btime L0(0; $allkwargs...) # 1.205 ns (0 allocations: 0 bytes)
@btime L1(0; $lastkwargs...) # 73.484 ns (3 allocations: 400 bytes)

```

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 21, 2020, 7:04pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/8 "2020-03-21T19:04:14Z")

</div>

> [@anon32405968](#):
>
> My last challenge then is this, how do I capture your `const` and `let` block logic within the above `freeze_params` function. It seems I was caught out by not considering the passing by sharing in Julia functions. One way I am wondering is if I can just copy the variable within the function so it forces local.

The problem with your freeze\_params is that it’s inside the `L0` function, not outside, so it just makes `L0` carry around the global from `freeze_params`. You can do this though:

```julia-auto
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

function freeze_params(func_to_freeze, params_to_freeze)
    (arg1; kwargs...) -> func_to_freeze(arg1; kwargs..., params_to_freeze...)
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

const L1 = freeze_params(L0, firstkwargs)

```

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 21, 2020, 7:06pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/9 "2020-03-21T19:06:40Z")

</div>

Another option if you want `L1` to be a regular function which can have methods added to it is to use `@eval`:

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

@eval L1(arg1; kwargs...) = L0(arg1; $(firstkwargs)..., kwargs...)

```

This will be just as fast as the `const let` form but can have methods added to it.

Using `eval` like this is a bad idea if you’re using it in a local scope though as your definition will ‘leak’:

```julia
julia> let
           function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
               arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
           end
       
           allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
           firstkwargs = (kwarg1=1, kwarg2=2)
           lastkwargs = (kwarg3=3, kwarg4=4)
       
           #@generated L1(arg1; kwargs...) = :(L0(arg1; $(firstkwargs)..., kwargs...))
           @eval L1(arg1; kwargs...) = L0(arg1; $(firstkwargs)..., kwargs...)
       end
L1 (generic function with 1 method)

julia> L1
L1 (generic function with 1 method)

```

so if these functions might get created in a local scope instead of global scope, you can use a `@generated` function instead:

```julia
@generated L1(arg1; kwargs...) = :(L0(arg1; $(firstkwargs)..., kwargs...))

```

which does not leak into the global scope:

```julia
julia> let
           function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
               arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
           end
       
           allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
           firstkwargs = (kwarg1=1, kwarg2=2)
           lastkwargs = (kwarg3=3, kwarg4=4)
       
           @generated L1(arg1; kwargs...) = :(L0(arg1; $(firstkwargs)..., kwargs...))
       end
(::var"#L1#12"{var"#L1#8#13"}) (generic function with 1 method)

julia> L1
ERROR: UndefVarError: L1 not defined

```

---

<div class="post-metadata">

**Author:** ![anon32405968](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@anon32405968](https://discourse.julialang.org/u/anon32405968)\
**Post date:** [March 21, 2020, 7:12pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/10 "2020-03-21T19:12:14Z")

</div>

I think so but I’m probably misunderstanding or making an error, when I run:

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

const L1 = let firstkwargs = firstkwargs
    (arg1; kwargs...) -> L0(arg1; firstkwargs..., kwargs...)
end

@btime L0(0; $allkwargs...) # 1.421 ns (0 allocations: 0 bytes)
@btime L1(0; $lastkwargs...) # 1.420 ns (0 allocations: 0 bytes)

```

Then I can do:

```julia
julia> L1(0; lastkwargs...)
10

julia> L1(0.0; lastkwargs...)
10.0

```

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 21, 2020, 7:14pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/11 "2020-03-21T19:14:36Z")

</div>

`L1` is still a generic function that can take in arguments of different types, but you can’t use multiple dispatch in the normal way to add different methods that do different things. Your example is just duck typing.

My example:

```julia
julia> begin
       function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
           arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
       end
       
       allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
       firstkwargs = (kwarg1=1, kwarg2=2)
       lastkwargs = (kwarg3=3, kwarg4=4)
       
       const L1 = let firstkwargs = firstkwargs
           (arg1; kwargs...) -> L0(arg1; firstkwargs..., kwargs...)
       end
       end
#8 (generic function with 1 method)

julia> L1(::Float64) = "hi"
ERROR: cannot define function L1; it already has a value
Stacktrace:
 [1] top-level scope at none:0
 [2] top-level scope at REPL[2]:1

```

is multiple dispatch

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 21, 2020, 7:20pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/12 "2020-03-21T19:20:54Z")

</div>

> [@Mason](#):
>
> so if these functions might get created in a local scope instead of global scope, you can use a `@generated` function instead:
> 
> ```julia
> @generated L1(arg1; kwargs...) = :(L0(arg1; $(firstkwargs)..., kwargs...))
> 
> ```
> 
> which does not leak into the global scope:

In case it was unclear, I should also note that a `@generated` function also works in the global scope just fine.

---

<div class="post-metadata">

**Author:** ![anon32405968](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@anon32405968](https://discourse.julialang.org/u/anon32405968)\
**Post date:** [March 21, 2020, 7:30pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/13 "2020-03-21T19:30:41Z")

</div>

@Mason thanks so much for all of the above, it is extremely helpful. I think I am starting to understand (particularly on duck typing), though I’ve always had difficulty understanding the `@generated` macro.

I’m trying to put it all together and hit an error message unfortunately:

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

function freeze_params(func_to_freeze, params_to_freeze)
    @generated out(arg1; kwargs...) = :(func_to_freeze(arg1; $(params_to_freeze)..., kwargs...))
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

L1 = freeze_params(L0, firstkwargs)

@btime L0(0; $allkwargs...) 
@btime L1(0; $lastkwargs...)

```

The error is:  
`ERROR: LoadError: syntax: Global method definition around C:\Users\J\Documents\Work\20200306_FWinvestigate.jl:771 needs to be placed at the top level, or use "eval".`

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 21, 2020, 7:38pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/14 "2020-03-21T19:38:14Z")

</div>

You’re still doing things in the wrong order and using non-constant globals.

The error you got is because you are trying to create a generated function that relies on data that doesn’t exist yet. In

```julia
function freeze_params(func_to_freeze, params_to_freeze)
    @generated out(arg1; kwargs...) = :(func_to_freeze(arg1; $(params_to_freeze)..., kwargs...))
end

```

`params_to_freeze` doesn’t have a value until the `freeze_params` function is executed, but the `@generated` macro runs at parse time which is before `freeze_params` is ever run.

---

<div class="post-metadata">

**Author:** ![anon32405968](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@anon32405968](https://discourse.julialang.org/u/anon32405968)\
**Post date:** [March 21, 2020, 7:40pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/15 "2020-03-21T19:40:56Z")

</div>

Thanks that makes sense

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 21, 2020, 7:41pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/16 "2020-03-21T19:41:35Z")

</div>

You can do this:

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

function freeze_params(func_to_freeze, params_to_freeze)
    (args...; kwargs...) -> func_to_freeze(args...; params_to_freeze..., kwargs...)
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

@generated L1(args...; kwargs...) = :($(freeze_params(L0, firstkwargs))(args...; kwargs...))

@btime L0(0; $allkwargs...) 
@btime L1(0; $lastkwargs...)

```

or instead have

```julia
@eval L1(args...; kwargs...) = $(freeze_params(L0, firstkwargs))(args...; kwargs...)

```

or

```julia
const L1 = freeze_params(L0, firstkwargs)

```

`const let` blocks, `@generated` functions and `@eval` all have their own strengths and weaknesses so which one is preferable depends on your situation.

---

<div class="post-metadata">

**Author:** ![anon32405968](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@anon32405968](https://discourse.julialang.org/u/anon32405968)\
**Post date:** [March 22, 2020, 4:06pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/17 "2020-03-22T16:06:02Z")

</div>

Thanks @Mason, I did some reading after this. It seems the `let` approach is most flexible so I am trying to do that. It seems this is the right way forward for me but I think I’m doing something wrong as it is not giving the speedup expected. ([Performance Tips · The Julia Language](https://docs.julialang.org/en/v1.4/manual/performance-tips/#man-performance-captured-1))

```julia
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

function freezeparams(func_to_freeze, params_to_freeze)
    (arg1; kwargz...) -> func_to_freeze(arg1; kwargz..., params_to_freeze...)
end

function freezeparams_let(func_to_freeze, params_to_freeze)
    f = let params_to_freeze = params_to_freeze
        (arg1; kwargs...) -> func_to_freeze(arg1; kwargs..., params_to_freeze...)
    end
    f
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

L1_FP_WITHARG_GLOBAL(arg1; kwargs...) = freezeparams(L0, firstkwargs)
L1_FP_WITHARG_INTERP(arg1; kwargs...) = freezeparams(L0, (kwarg1=1, kwarg2=2))

L1_FP_NOARG_GLOBAL = freezeparams(L0, firstkwargs)
L1_FP_NOARG_INTERP = freezeparams(L0, (kwarg1=1, kwarg2=2))

L1_FPL_WITHARG_GLOBAL(arg1; kwargs...) = freezeparams_let(L0, firstkwargs)
L1_FPL_WITHARG_INTERP(arg1; kwargs...) = freezeparams_let(L0, (kwarg1=1, kwarg2=2))

L1_FPL_NOARG_GLOBAL = freezeparams_let(L0, firstkwargs)
L1_FPL_NOARG_INTERP = freezeparams_let(L0, (kwarg1=1, kwarg2=2))

println("\nReference case with no freezing")
@btime L0(0; $allkwargs...) # 1.205 ns (0 allocations: 0 bytes)

println("\nL1_FP_WITHARG_GLOBAL")
@btime L1_FP_WITHARG_GLOBAL(0; $lastkwargs...) # 271.585 ns (2 allocations: 64 bytes)

println("\nL1_FP_WITHARG_INTERP")
@btime L1_FP_WITHARG_INTERP(0; $lastkwargs...) # 1.506 ns (0 allocations: 0 bytes)

println("\nL1_FP_NOARG_GLOBAL")
@btime L1_FP_NOARG_GLOBAL(0; $lastkwargs...) # 43.997 ns (1 allocation: 32 bytes)

println("\nL1_FP_NOARG_INTERP")
@btime L1_FP_NOARG_INTERP(0; $lastkwargs...) # 43.092 ns (1 allocation: 32 bytes)

println("\nL1_FPL_WITHARG_GLOBAL")
@btime L1_FPL_WITHARG_GLOBAL(0; $lastkwargs...) # 269.493 ns (2 allocations: 64 bytes)

println("\nL1_FPL_WITHARG_INTERP")
@btime L1_FPL_WITHARG_INTERP(0; $lastkwargs...) # 1.506 ns (0 allocations: 0 bytes)

println("\nL1_FPL_NOARG_GLOBAL")
@btime L1_FPL_NOARG_GLOBAL(0; $lastkwargs...) # 42.490 ns (1 allocation: 32 bytes)

println("\nL1_FPL_NOARG_INTERP")
@btime L1_FPL_NOARG_INTERP(0; $lastkwargs...) # 42.490 ns (1 allocation: 32 bytes)

```

The most unexpected thing I noticed was that there seems to be a performance difference when assigning an annonymous function (output of `freezeparams`) to a variable (e.g. `f`) or a function (e.g. `f(arg1; kwargs...)=`) with the second one seemingly faster.

Regarding the capturing of the global variable, the `let` block appears not to make a difference (though i could reproduce the example in the Julia docs). The only thing improving the situation is directly splicing the parameters to freeze in the function call. (Which is an acceptable limitation of the software we’re writing.)

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 22, 2020, 4:15pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/18 "2020-03-22T16:15:42Z")

</div>

This `let` block doesn’t do anything:

```julia
function freezeparams_let(func_to_freeze, params_to_freeze)
    f = let params_to_freeze = params_to_freeze
        (arg1; kwargs...) -> func_to_freeze(arg1; kwargs..., params_to_freeze...)
    end
    f
end

```

The inside of the function is already a local scope. Your problems are happening in the global scope.

Everything on the left hand side of the equals signs here:

```julia
L1_FP_NOARG_GLOBAL = freezeparams(L0, firstkwargs)
L1_FP_NOARG_INTERP = freezeparams(L0, (kwarg1=1, kwarg2=2))

L1_FPL_WITHARG_GLOBAL(arg1; kwargs...) = freezeparams_let(L0, firstkwargs)
L1_FPL_WITHARG_INTERP(arg1; kwargs...) = freezeparams_let(L0, (kwarg1=1, kwarg2=2))

L1_FPL_NOARG_GLOBAL = freezeparams_let(L0, firstkwargs)
L1_FPL_NOARG_INTERP = freezeparams_let(L0, (kwarg1=1, kwarg2=2))

```

are non-constant global varaibles, so running `L1_FP_NOARG_INTERP(0; lastkwargs...)` for instance is going to be slow because the compiler has to check what `L1_FP_NOARG_INTERP` is at every step.

Why are you not using any of the techniques I suggested above? I feel a little frustrated as I put a fair amount of work into writing them out and explaining them but then you’re not using them.

---

<div class="post-metadata">

**Author:** ![anon32405968](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@anon32405968](https://discourse.julialang.org/u/anon32405968)\
**Post date:** [March 22, 2020, 6:10pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/20 "2020-03-22T18:10:46Z")

</div>

> [@Mason](#):
>
> This `let` block doesn’t do anything:

I may have misunderstood [Performance Tips · The Julia Language](https://docs.julialang.org/en/v1.4/manual/performance-tips/#man-performance-captured-1) but it has the example:

```julia
function abmult3(r::Int)
    if r < 0
        r = -r
    end
    f = let r = r
            x -> x * r
    end
    return f
end

```

and says that “_The `let` block creates a new variable `r` whose scope is only the inner function. The second technique recovers full language performance in the presence of captured variables._” (Beneath the final example of that section.)

> [@Mason](#):
>
> Why are you not using any of the techniques I suggested above? I feel a little frustrated as I put a fair amount of work into writing them out and explaining them but then you’re not using them.

Sorry you are frustrated. I certainly appreciate the help. I have tried to implement them but my actual use case is more complicated than the above examples and I have not been able to implement succesfully. Ideally `freeze_params` would return a `struct` containing four functions, all of which would be frozen in the way above. And the behaviour of `freeze_params` should be the same whether used in global scope or called by the user within a function (in the latter case we want to avoid any world-age issues). Also we are trying to avoid user-facing macros and `const` keywords if possible as a key audience for our software will be people who have no experience with Julia before, and perhaps very little programming experience in general. **I am going to try again with your methods this evening and will report back with any progress.**

Edit: also, for some reason it will not let me mark your first response (or any of them) as the solution, otherwise I would have done so. Apologies I’m not sure why that is the case.

Links to something closer to what we are trying to do:

> <https://github.com/JuliaRheology/RheoBenchAndTest/blob/9b43510c358077130143affd540848df1a6c106e/20200306_FWinvestigate.jl#L621>

> <https://github.com/JuliaRheology/RheoBenchAndTest/blob/9b43510c358077130143affd540848df1a6c106e/20200321_closuresinvestigate.jl#L119>

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [March 22, 2020, 8:43pm UTC](https://discourse.julialang.org/t/why-is-this-closure-over-keyword-arguments-causing-such-a-dramatic-slowdown/36308/21 "2020-03-22T20:43:15Z")

</div>

> [@anon32405968](#):
>
> I may have misunderstood [Performance Tips · The Julia Language](https://docs.julialang.org/en/v1.4/manual/performance-tips/#man-performance-captured-1) but it has the example:
> 
> ```julia-auto
> function abmult3(r::Int)
> if r < 0
> r = -r
> end
> f = let r = r
> x -> x * r
> end
> return f
> end
> 
> ```
> 
> and says that “ _The `let` block creates a new variable `r` whose scope is only the inner function. The second technique recovers full language performance in the presence of captured variables._ ” (Beneath the final example of that section.)

Ah yes, but this is a separate issue, at least from the one in your minimal working example. The issue you’re having as far as I can tell is not that varaibles in your closures are boxed but that you’re depending on globals.

> [@anon32405968](#):
>
> Sorry you are frustrated. I certainly appreciate the help. I have tried to implement them but my actual use case is more complicated than the above examples and I have not been able to implement succesfully. Ideally `freeze_params` would return a `struct` containing four functions, all of which would be frozen in the way above. And the behaviour of `freeze_params` should be the same whether used in global scope or called by the user within a function (in the latter case we want to avoid any world-age issues). Also we are trying to avoid user-facing macros and `const` keywords if possible as a key audience for our software will be people who have no experience with Julia before, and perhaps very little programming experience in general. **I am going to try again with your methods this evening and will report back with any progress.**

Just in case I came off a bit gruff, I’m not angry at you or anything and I am happy to help. I’m just sensing that not everything I’m saying is coming across and I’m having trouble figuring out how to communicate more clearly.

Can you explain why doing this does not solve your problem:

```julia-auto
function L0(arg1; kwarg1, kwarg2, kwarg3, kwarg4)
    arg1 + kwarg1 + kwarg2 + kwarg3 + kwarg4
end

function freezeparams(func_to_freeze, params_to_freeze)
    (arg1; kwargz...) -> func_to_freeze(arg1; kwargz..., params_to_freeze...)
end

allkwargs = (kwarg1=1, kwarg2=2, kwarg3=3, kwarg4=4)
firstkwargs = (kwarg1=1, kwarg2=2)
lastkwargs = (kwarg3=3, kwarg4=4)

const L1 = freezeparams(L0, firstkwargs)

@btime L0(0; $allkwargs...)
@btime L1(0; $lastkwargs...)

```

i.e. putting `const` in front of `L1 = ...`?
