-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
In --trace-compile
Tag nested precompiles with # nested const compilation
.
#59366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Just to understand, by "nested" here you mean it's not the top-level function call being invoked when triggering the precompilation, so that the user knows that it's sufficient to do precompile(Tuple{typeof(Main.f1), Int64}) to precompile |
From what I understand, there is a dynamic dispatch so it is not enough to run that su gle precompile signature because you don't statically resolve the nested call. |
This is a dynamic dispatch that is executed (always in an identical way, with identical arguments, etc.) during a concrete-eval of some compile-time function, so it is statically resolved / implied by the outer |
Exactly like Cody said. This is a weird edge-case that I only learned about last week, but apparently during compilation, the compiler can decide that a function should evaluate to a compile-time-constant, but Inference can't evaluate the result on its own: the compiler needs to actually run the function to see what it returns. So during compilation, the compiler does a dynamic dispatch to that function (which compiles it and logs this precompile statement), runs it to get the Const return value, and then proceeds compiling the original function. Since there's a dynamic dispatch, we log the precompile statement. But as Cody says, it is fully implied by the outer call.. So probably you could skip it if you were replaying compilation logs for snooping, but the end result should be exactly identical if you do run it. And it's kind of nice to log it, since it is a dynamic dispatch, but i think we need to clarify to the user that this isn't a normal precompile statement. Maybe we can find a better term for this than Maybe we could call this |
```julia ./usr/bin/julia --startup=no --trace-compile=stderr -e ' Base.@assume_effects :foldable function nested1(x) sum(collect(x for _ in 1:10_000_000)) end f1(x) = nested1(sizeof(x)) + x f1(2)' precompile(Tuple{typeof(Main.nested1), Int64}) # nested_compile precompile(Tuple{typeof(Main.f1), Int64}) ./usr/bin/julia --startup=no --trace-compile=stderr --trace-compile-timing -e ' Base.@assume_effects :foldable function nested1(x) sum(collect(x for _ in 1:10_000_000)) end f1(x) = nested1(sizeof(x)) + x f1(2)' #= 8.1 ms =# precompile(Tuple{typeof(Main.nested1), Int64}) # nested_compile #= 71.3 ms =# precompile(Tuple{typeof(Main.f1), Int64}) ```
2c6ebab
to
7d6e467
Compare
It's a description so no need for underscores? |
I think I was incorrect when I said this.. Effects analysis seems too limited right now to construct a practical example, but the definition of It only requires that the return value is a deterministic function of the arguments (via egality), so this would be legal: Base.@assume_effects :foldable function complicated_identity(N::Int)
T = rand(Bool) ? typeof(N) : Any
data = T[N,] # temporary vector, so not an observable side-effect (still foldable)
return only(data) # the method invoked here is not consistent, but the result is
end and does not emit the same "nested" precompile for every execution. |
We should probably be enforcing that any options (like precompile dispatch counting) are forcibly disabled while running the compiler. You do not want to ignore these after the fact or mark them differently in the output, since then you will miss actual real dispatches that occur at runtime simply because they also could occur during compile. |
Ah, that's an interesting point. So you're saying we should somehow "forget" that we've compiled this, and then the first time it's dispatched to again in the future -- outside the compiler -- we should log it as if we'd precompiled it then? 🤔 that seems difficult. I do see the merit, but that seems like a much harder code change, and i'm not entirely sure of the merit. I think that probably people shouldn't skip those during compilation replay for snooping, to be careful about the situation you described. We mostly just wanted to annotate these to avoid double-counting during post-processing aggregation for our observability data: "how much time did this engine spend compiling, according to the precompile traces?" (We also look at other metrics like Maybe this needs a paragraph in a documentation page about the --trace-compile feature to explain the nuances above? |
Co-authored-by: Ian Butterworth <i.r.butterworth@gmail.com>
So, @IanButterworth / @gbaraldi: Any thoughts on this? I agree with @vtjnash that long-term we might want to think about not emitting precompile statements at all for these, or doing some other kind of math to subtract the compilation time from their parents or something like that. But for now, i think just tagging these so that the user can differentiate them is a good improvement. Thoughts from your end? |
Perhaps adding it to the docs would help outlining the motivation to highlight it? Without docs I can see users like myself not understanding what it means |
Tests if possible too? News too. |
--trace-compile
Tag nested precompiles with # nested_compile
.--trace-compile
Tag nested precompiles with # nested const compilation
.
…const compilation`. (JuliaLang#59366) Backports JuliaLang#59366.
In
--trace-compile
Tag nested precompiles with# nested_compile
.Before:
After: