-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
optimization request: automatically outline throws #29688
Comments
Ref #23281 Regarding
I think one of the complications is in things like function f(a, b)
check_cond() || throw(SomeError("This was the error $(repr(a)): $b"))
end which is equivalent to function f(a, b)
if !check_cond()
_tmp1 = string(repr(a))
_tmp2 = string(b)
_tmp3 = string("This was the error ", _tmp1, ": ", _tmp2)
_tmp4 = SomeError(_tmp3)
throw(_tmp4)
end
end Clearly, just outlining the |
Something analogous to this (I think, correct me if I'm wrong) impacts overflow/underflow checks in various simple operations (e.g. in complex division, which could be made ~20% faster by outlining). A minimal example is: function checkedadd(x::Float64, y::Float64)
z=y*rand()
if z>1.0e16 # some _unlikely_ scaling operation
z/=rand()
end
return x+z
end
@noinline function scale_outlined(z::Float64)
return z/rand()
end
function checkedadd_outlined(x::Float64, y::Float64)
z=y*rand()
if z>1.0e16 # some _unlikely_ scaling operation
z=scale_outlined(z)
end
return x+z
end The straightforward, non-outlined version of
(on the other hand, if the supposedly unlikely condition is actually met, the outlined version is slower---but that won't matter in the vast majority of overflow/underflow-related cases.) |
It's a very simple version of escape analysis, no? Could we simply outline all blocks that are determine to end with an unreachable ( |
This will not modify the local variable |
Ah, thanks - it doesn't change the outcome though. I've updated the previous comment to correctly modify the variable |
You can just outline the basic block a function f(a, b)
if rand() < 0.1
_tmp1 = string(repr(a))
_tmp2 = string(b)
_tmp3 = string("This was the error ", _tmp1, ": ", _tmp2)
_tmp4 = ErrorException(_tmp3)
throw(_tmp4)
end
end julia> @code_lowered f(1, 2)
CodeInfo(
2 1 ─ Core.NewvarNode(:(_tmp1)) │
│ Core.NewvarNode(:(_tmp2)) │
│ Core.NewvarNode(:(_tmp3)) │
│ Core.NewvarNode(:(_tmp4)) │
│ %5 = (Main.rand)() │
│ %6 = %5 < 0.1 │
└── goto #3 if not %6 │
3 2 ─ %8 = (Main.repr)(a) │
│ _tmp1 = (Main.string)(%8) │
4 │ _tmp2 = (Main.string)(b) │
5 │ _tmp3 = (Main.string)("This was the error ", _tmp1, ": ", _tmp2) │
6 │ _tmp4 = (Main.ErrorException)(_tmp3) │
7 │ %13 = (Main.throw)(_tmp4) │
└── return %13 │
3 ─ return │
) The basic block containing the 3 2 ─ %8 = (Main.repr)(a) │
│ _tmp1 = (Main.string)(%8) │
4 │ _tmp2 = (Main.string)(b) │
5 │ _tmp3 = (Main.string)("This was the error ", _tmp1, ": ", _tmp2) │
6 │ _tmp4 = (Main.ErrorException)(_tmp3) │
7 │ %13 = (Main.throw)(_tmp4) │
└── return %13 │ So that's what you would outline into its own function body. This approach seems pretty simple to me. It's not as general as an optimization that figures out when outlining something like scaling would improve performance, but honestly, I think that's a pretty different beast and is a case where it's perfectly reasonable for someone to do some manual outlining. |
My comment was mostly regarding
and the note that it is not just the throwing of the error you want to outline, but also the creation of the inputs to the error. |
Taking the entire basic block addresses that in most cases: that includes all "straight line" code leading up to the throw. Of course, you may want to do something a little more aggressive and outline any set of basic blocks that can only lead to the throw call. That would also handle cases like this: function f(a, b)
rand() < 0.1 && throw(ErrorException("Blah $a: $(rand(Bool) ? b : "meh")"))
return a/b
end julia> @code_lowered f(1, 2)
CodeInfo(
2 1 ─ %1 = (Main.rand)() │
│ %2 = %1 < 0.1 │
└── goto #6 if not %2 │
2 ─ %4 = (Main.rand)(Main.Bool) │
└── goto #4 if not %4 │
3 ─ #temp# = b │
└── goto #5 │
4 ─ #temp# = "meh" │
5 ┄ %9 = #temp# │
│ %10 = (Base.string)("Blah ", a, ": ", %9) │
│ %11 = (Main.ErrorException)(%10) │
│ (Main.throw)(%11) │
└── goto #6 │
3 6 ─ %14 = a / b │
└── return %14 │
) Basic blocks 2, 3, 4 and 5 should all be outlined. This could be determined by post-domination. |
@Liozou did some experiments on outlining? I don't recall if he managed to do the outlining from within the optimizer. |
I did some attempts but unfortunately I was far from producing anything concrete really... The two main difficulties were:
|
How about |
Name it |
That's a pretty clever fast-and-dirty way to do it and way easier than the compiler pass. Could also potentially do caching and deduplication based on the outlined expression AST. |
Interesting. Another possible name could be function f(a, b)
@unlikely rand() < 0.1 && throw(ErrorException("Blah $a: $(rand(Bool) ? b : "meh")"))
return a/b
end |
Outlining should be strickly harder and less optimum than improving the compiler. |
Improving the compiler to do this would be great. If you do decide to use a macro, feel free to import whatever you like from |
No, not to improve the compiler to do this, but to make allocation in branch not an issue anymore. |
Oh, by creating the GC frame only on the branch? I'd read some earlier comments about that and for some reason I assumed it was already done in 1.0. |
We're increasingly seeing this kind of manual optimization in Base code:
rewritten as
It would be great if the compiler could figure this out and do the transformation automatically. I don't think it needs to be terribly complicated either:
throw
should never be a common code path in Julia and throwing each kind of error could be its own outlined function, which would avoid an explosion of these automatically outlined throw functions.The text was updated successfully, but these errors were encountered: