# ANN: New package SnoopPrecompile

**URL:** https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778
**Category:** Community
**Tags:** package, ttfx, latency
**Created:** [July 25, 2022, 9:40pm UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778 "2022-07-25T21:40:56Z")
**Posts on this page:** 14
**Page:** 2

<div class="post-metadata">

### Author: ![ToucheSir](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/touchesir/32/14411_2.png) [@ToucheSir](https://discourse.julialang.org/u/ToucheSir)
#### Post date: [August 4, 2022, 12:35am UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/21 "2022-08-04T00:35:01Z")

</div>

Having looked into this before, most of that 11s is fixed overhead from having to precompile + codegen + run the AD transform code. Trying out SnoopPrecompile with the following MWE:

```julia
using Zygote, SnoopCompileCore

f(x) = (2x + 1) / 3 - 4

tinf = @snoopi_deep gradient(f, 1.)

using SnoopCompile

@show tinf

```

I get:

```julia
tinf = InferenceTimingNode: 6.153391/11.348825 on Core.Compiler.Timings.ROOT() with 306 direct children # master
tinf = InferenceTimingNode: 6.098876/6.680918 on Core.Compiler.Timings.ROOT() with 87 direct children # https://github.com/FluxML/Zygote.jl/pull/1281

```

For a larger MWE (TTFG on `Metalhead.ViT`):

```julia
InferenceTimingNode: 18.848347/43.728851 on Core.Compiler.Timings.ROOT() with 906 direct children # master
InferenceTimingNode: 18.814999/37.941746 on Core.Compiler.Timings.ROOT() with 532 direct children # PR 1281

```

So it doesn’t scale, but slashing that constant overhead (and basically all the type inference-related overhead) by 40% is pretty good! If anyone has tips on how to reduce the ~300ms load time increase, please leave them on the PR so we can merge this asap.

---

<div class="post-metadata">

### Author: ![marius311](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marius311/32/3953_2.png) [@marius311](https://discourse.julialang.org/u/marius311)
#### Post date: [August 4, 2022, 6:03am UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/22 "2022-08-04T06:03:06Z")

</div>

Are you sure its quite that? To pick a random `rrule`, it does look like its the same gensym-named type every time:

```julia
$ julia -e "using ChainRules; println(typeof(ChainRules.rrule(Array, [1,2,3])[2]))"
ChainRules.var"#Array_pullback#1301"{ChainRulesCore.ProjectTo{AbstractArray, NamedTuple{(:element, :axes), Tuple{ChainRulesCore.ProjectTo{Float64, NamedTuple{(), Tuple{}}}, Tuple{Base.OneTo{Int64}}}}}}
$ julia -e "using ChainRules; println(typeof(ChainRules.rrule(Array, [1,2,3])[2]))"
ChainRules.var"#Array_pullback#1301"{ChainRulesCore.ProjectTo{AbstractArray, NamedTuple{(:element, :axes), Tuple{ChainRulesCore.ProjectTo{Float64, NamedTuple{(), Tuple{}}}, Tuple{Base.OneTo{Int64}}}}}}

```

Or does it need to be more than that to qualify for being the same c.f. precompilation?

---

<div class="post-metadata">

### Author: ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)
#### Post date: [August 4, 2022, 10:35am UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/23 "2022-08-04T10:35:52Z")

</div>

> [@marius311](#):
>
> Are you sure its quite that? To pick a random `rrule`, it does look like its the same gensym-named type every time:
> 
> ```julia
> 
> ```

In one session, run the rule directly. In the next session, create an anonymous function first and then run the rule. `#1301` means 1301 anonymous functions were created first (holy hell). But if you create one and then run the code, you should bump that counter by one first and it’ll be different.

---

<div class="post-metadata">

### Author: ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)
#### Post date: [August 4, 2022, 11:29am UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/24 "2022-08-04T11:29:30Z")

</div>

Is it possible to write a macro to automatically convert closures to callable structs? My confusion is that the struct definition should be evaluated in top-level scope rather than inside a parent function. I don’t know if macros can easily do that. Maybe `eval` can be emitted by the macro to do exactly this?

---

<div class="post-metadata">

### Author: ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)
#### Post date: [August 4, 2022, 11:35am UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/25 "2022-08-04T11:35:33Z")

</div>

I always thought the counter was module-specific, is that not the case? If not, it would not be breaking to make it so, right?

---

<div class="post-metadata">

### Author: ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)
#### Post date: [August 4, 2022, 11:41am UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/26 "2022-08-04T11:41:26Z")

</div>

I’m not sure about that.

But note that wouldn’t solve the issue though. There’s still a possibility that someone puts in `using` `if time() < 500000000; (x) -> x+2; end` and thus in theory even in a module you couldn’t be absolutely certain that the anonymous function counter would be the same every single time you did `using`. And if you cannot guarantee that, then you cannot cache the precompile for the type, which is why anonymous functions are essentially an opt-out of precompilation. The inner functions that they call are usually the “meat” of it, so it’s usually okay (what they call inside can still precompile of course, so a closure will have a non-precompiled thing over a precompiled thing, if you snoopprecompile the function that would be closed), but ChainRules is definitely an exception here.

---

<div class="post-metadata">

### Author: ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)
#### Post date: [August 4, 2022, 1:07pm UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/27 "2022-08-04T13:07:38Z")

</div>

I think the names are given at parse/lowering time, so it doesn’t matter if they’re in unreachable branches. For example:

```julia
function test()
    if false
        x = map(y -> y + 1, 1:10)
    else
        x = map(y -> y + 2, 1:10)
    end
end

@code_lowered test()

```

```julia
test (generic function with 1 method)

CodeInfo(
1 ─ Core.NewvarNode(:(#14))
│ Core.NewvarNode(:(#13))
│ Core.NewvarNode(:(x))
└── goto #3 if not false
2 ─ #13 = %new(Main.:(var"#13#15"))
│ %6 = #13
│ %7 = 1:10
│ %8 = Main.map(%6, %7)
│ x = %8
└── return %8
3 ─ #14 = %new(Main.:(var"#14#16"))
│ %12 = #14
│ %13 = 1:10
│ %14 = Main.map(%12, %13)
│ x = %14
└── return %14
)

```

Also anecdotally, we precompile the (usually anonymous) listeners functions of observables in Makie.jl, and that seems to work fine.

---

<div class="post-metadata">

### Author: ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)
#### Post date: [August 4, 2022, 3:38pm UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/28 "2022-08-04T15:38:11Z")

</div>

If you do that at the top level of a module will it lower both?

---

<div class="post-metadata">

### Author: ![marius311](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marius311/32/3953_2.png) [@marius311](https://discourse.julialang.org/u/marius311)
#### Post date: [August 4, 2022, 7:55pm UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/29 "2022-08-04T19:55:33Z")

</div>

> [@jules](#):
>
> I always thought the counter was module-specific, is that not the case?

This is what I thought as well. This seems to confirm it?

```julia
julia -e "using ChainRules; println(typeof(ChainRules.rrule(Array, [1,2,3])[2]))"
julia -e "using ChainRules; (()->1)(); println(typeof(ChainRules.rrule(Array, [1,2,3])[2]))"
julia -e "using ChainRules; @eval ChainRules (()->1)(); println(typeof(ChainRules.rrule(Array, [1,2,3])[2]))"
# all print same type

```

So I’m still not sure its not something else more subtle breaking precompilation with Zygote/ChainRule’s closures.

---

<div class="post-metadata">

### Author: ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)
#### Post date: [August 4, 2022, 8:16pm UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/30 "2022-08-04T20:16:23Z")

</div>

Ok this test is a bit weird but I think it shows the behavior. I define a module with a dead branch with an anonymous function. I can’t call `@code_lowered` on that but on another function that I define after. The anonymous function it uses is then called `var"#3#4")`:

```julia
julia> module M
           if false
               map(y -> y + 1, 1:10)
           end
           f(xs) = map(x -> x + 1, xs)
       end
Main.M

julia> @code_lowered M.f(1:3)
CodeInfo(
1 ─ #3 = %new(Main.M.:(var"#3#4"))
│ %2 = #3
│ %3 = Main.M.map(%2, xs)
└── return %3
)

```

If I run this again and replace the module, the anonymous function is still named `var"#3#4"`, so it doesn’t depend on global state. The first anonymous function in the dead branch should therefore be `var"#1#2` although I don’t know what the two numbers refer to.

```julia
julia> module M
           if false
               map(y -> y + 1, 1:10)
           end
           f(xs) = map(x -> x + 1, xs)
       end
WARNING: replacing module M.
Main.M

julia> @code_lowered M.f(1:3)
CodeInfo(
1 ─ #3 = %new(Main.M.:(var"#3#4"))
│ %2 = #3
│ %3 = Main.M.map(%2, xs)
└── return %3
)

```

If I add another anonymous function in the dead branch, the name is shifted further down to `var"#5#6"`.

```julia
julia> module M
           if false
               map(y -> y + 1, 1:10)
               map(y -> y + 1, 1:10)
           end
           f(xs) = map(x -> x + 1, xs)
       end
WARNING: replacing module M.
Main.M

julia> @code_lowered M.f(1:3)
CodeInfo(
1 ─ #5 = %new(Main.M.:(var"#5#6"))
│ %2 = #5
│ %3 = Main.M.map(%2, xs)
└── return %3
)

```

If I do one more, it’s `var"#7#8"` and so on.

---

<div class="post-metadata">

### Author: ![marius311](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marius311/32/3953_2.png) [@marius311](https://discourse.julialang.org/u/marius311)
#### Post date: [August 4, 2022, 9:59pm UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/31 "2022-08-04T21:59:32Z")

</div>

Maybe I’m not following your discussion but aren’t changes to the module irrelevant here, since those will trigger precompilation of that module as well as of the dependent and snoop-precompiling other modules? The question for me seem to be whether the types of closures in a precompiled module ever change, and the answer there seems to be no? (such that Chris’ original explanation for the inefficacy of SnoopPrecompile on Zygote doesn’t seem right)

---

<div class="post-metadata">

### Author: ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)
#### Post date: [August 5, 2022, 5:33am UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/32 "2022-08-05T05:33:28Z")

</div>

This was about whether things like loading order of things outside modules affect anonymous function names in modules (seems they don’t) and whether logic inside the module could affect the naming (it doesn’t). I think the only way might be to `@eval` stuff conditionally?

---

<div class="post-metadata">

### Author: ![tknopp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tknopp/32/3569_2.png) [@tknopp](https://discourse.julialang.org/u/tknopp)
#### Post date: [September 30, 2022, 3:10pm UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/33 "2022-09-30T15:10:21Z")

</div>

I just tried this package in MRIReco.jl and my TTFIR (time to first image reconstruction) dropped from 20 seconds to 3.6 seconds. using time went up about 3 seconds. Hence a clear net win. Impressive.

---

<div class="post-metadata">

### Author: ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)
#### Post date: [October 4, 2022, 12:52pm UTC](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778/34 "2022-10-04T12:52:40Z")

</div>

8 posts were split to a new topic: [Question about using SnoopPrecompile](https://discourse.julialang.org/t/question-about-using-snoopprecompile/88227)

[Previous page](https://discourse.julialang.org/t/ann-new-package-snoopprecompile/84778.md?page=1)
