Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add top-level compile time to at-time report.
As discussed in the comments for JuliaLang#37678, the `% compilation time` metric added to `@time` is not an accurate measurement of the total compilation time. It is in fact excluding any top-level compilation time, which may in fact be quite expensive if the body of your block is fully type stable and inferrable. This change adds a final print to the report for the top-level compilation time that was spent compiling your block. For example: ```julia julia> @eval function f(a) $(quote $([:(a = a + a * 10 + (a > 0 ? $i : 0.2)) for i in 1:100]...) end) $(quote $([:(a = a + a * 10 + (a > 0 ? $i : 0.2)) for i in 1:100]...) end) return a end f (generic function with 1 method) julia> @time f(2) 0.000000 seconds + 0.474445 seconds top-level compilation time -2.53151813758841e205 julia> x = 2 2 julia> @time f(x) # Inferring with a non-const is slower, but does not happen at top-level, since x is a global whose type cannot be known until runtime. 1.212882 seconds (129.51 k allocations: 4.811 MiB, 100.00% compilation time) -2.53151813758841e205 julia> @time f(x) # But now, subsequent invocations have no compilation time since it's already compiled. 0.000006 seconds (1 allocation: 16 bytes) -2.53151813758841e205 ``` Without this change, the `@time f(2)` call, would have reported `0.0 seconds` with no other context. The following example shows both types of compilation time reported: ```julia julia> @time peakflops() 0.803850 seconds (2.26 M allocations: 181.699 MiB, 4.90% gc time, 84.81% compilation time) + 0.017840 seconds top-level compilation time 1.5840888414049457e11 ``` ---- This change is fairly complex. Here's why: The trouble with the `@time` macro is that the user's code is compiled as _part of_ compiling the enclosing block, so the naive implementation of this change would report _every invocation_ as having some compilation time. To address this, we use a heuristic: we record the time to compile an _empty block_ that simply times an empty statement, and subtract that empty block compilation time from the time to compile the user's block, to get the true user compilation time. We also subtract the inner, dynamic compilation time (the number used to report the percentage), to get the top-level compilation time. Finally, we only report the top-level compilation time if it is suffiently larger than the empty block's compilation time by a ratio threshold, to prevent reporting compilation time due to noisy variance. It's annoyingly tricky, but we think it works, and achieves the best of all the above worlds regarding reporting these metrics. :) The user won't be fooled by `@time` reporting a much shorter time than the actual wall time because we actually spent 5 min inferring the code (this has happened to me in real life, and confused me for days). And the user _also_ won't be confused to see top-level compilation time where they don't expect it. And the total runtime reported remains only the time to execute their function, which is what they likely expect. It's complex, but we think this is a nice balance. :) Co-Authored-By: Valentin Churavy <[email protected]>
- Loading branch information