Jan Hubicka a écrit :
Sylvain Pion a écrit :
Naive user question : is this going to improve the efficiency
of throwing exceptions, at least in the restricted cases of :
There is little improvement already via EH cleanup: at least
cleanups/catch regions that turns out to be empty are now eliminated and
does not slow down unwinding process.
It will probably not help for the synthetic benchmark case of PR 6588
(try { throw(0); } catch(...) {}), since this case has nothing to be
cleaned up, right? Not that I care about this synthetic case, but
even if the case which has nothing to clean up nor unwinding is already
too slow...
I have no idea how much performance it adds, but cleanups that optimize
to nothing are quite common so it might show up in code that is bound in
performance by EH delivery speed (I don't expect this is that common,
right?)
Indeed, it's probably not common. However, IMO, the reason it's not more
common is probably that programmers realize that they need to avoid using
exceptions where they could be more natural to use (in terms of programming
style) only when they hit the performance penalty. And then, experienced
programmers ban them because they know they can't count on compilers to
get reasonnable efficiency.
EH throwing is so costly (20000 cycles minimum reported in PR 6588) that,
in some cases, even if it's exceptional, like a 10^-4 probability
of throwing, you will see it show up on the profile.
Having EH delivery at reasonnable speed would really open up the design
space : it would allow to use a non-intrusive way of reporting "exceptional"
return values in many more places. By non-intrusive, I mean that you
don't need to change the return type of your functions and manually
propagate your exceptional return values (or worse, use global or
thread-local variables).
So, in practice, EH is probably used for std::bad_alloc and such really
rare situations, but I think there are lots of other useful places it
could be used if it was faster.
( I know at least one, but people might think it's too specialized ;) )
- the catch clause contains the throw (after inlining).
^^^^^^^^^^^^
I meant the try block, sorry.
"inlining" throw() call is another item I forgot on my list. My EH-fu
is not exactly on par here. I see no reason why we can't convert throw() call
into FILTER_EXPR/OBJ_REF_EXPR set and RESX when RESX will be turned into
direct goto. It is also something I wanted to experiment with.
A few GCC developers have put some (now a bit old) comments in PR 6588
around this.
This has potential to improve C++ code especially by turning functions
leaf and cheaper for inlining. Definitly on tramp there are such cases.
One thing I worry about is that this effectivly makes it impossible to
breakpoint on throw that might be common thing C++ users want to see
working. This can probably be partly fixed by emitting proper dwarf for
inlined function around the RESX, but GDB won't use it at a moment for
breakpointing anyway.
Right, breakpointing on throw is useful for debugging, and some option
like -g probably needs to preserve this behavior.
We also should get profile estimates more precise here by knowing that
certain calls must throw. We are completely wrong here by basically
assuming that EH is never taken.
Indeed... At least throw() throws with a good probability :)
I'm not sure how far this would go, but it can't hurt to model
the program's behavior more correctly.
- interprocedural analysis can connect the throwing spot and
the corresponding catch clause.
?
One can work out interprocedurally where the exception will be caught,
but what one can do about this info? Translating it to nonlocal goto
is overkill because of colateral damage nonlocal goto
infrastructure would bring on the non-throwing path. If EH delivery
mechanizm has to be used, we are pretty much going as far as we can
here. In the throwing function we work out there is no local catch
block and in the callers similarly.
RTH has put a comment in PR 6588 that hints that the reason it's so slow
might be that the EH delivery mechanism is very general, in particular
it handles cross-language/libraries EH propagation.
So, I was thinking that, in the case where interprocedural analysis
knows the full path, and it does not go through unknown libraries
(and LTO might even increase the number of such cases in the future),
a less general way of delivering the exceptions could be used, which
would be faster by not bothering to check the cases that are known
not to be possible like going through external libraries/languages.
Maybe that's too naive because I don't know what's happenning behind
throw() in enough detail to understand what makes it so slow.
(my naive view of stack unwinding is that you just have to find
a stack pointer per call level, and make sure you run the destructors
in order, and do a few type comparisons for finding the right catch
clause, so how can this be so slow is a complete mystery to me...).
Pure-const pass can probably be improved to, in addition to mark NOTHROW
function to also work out list of types of exceptions. Has this chance
to help?
Hey, maybe... At least it can help removing some catch() clauses for
types which can never match. It can also help prove that the function
g() below is NOTHROW :
void f() { throw(0); } // it's not NOTHROW, but it would be THROW<int>
void g() { try { f(); } catch (int) {} }
// is NOTHROW, but can only be proven is we know f()
// only throws a type which is caught here
What would be the impact in practice, I don't really know...
(but it sounds cool to have the compiler know this :) )
I can imagine that if one uses a library which internally plays
with exceptions, but never leaks one, then your proposal would
allow to have the caller be properly optimizde by assuming nothrow.
Also if you are aware of EH heavy codebase that can be nicely
benchmarked, it would be interesting to consider adding it into our C++
benchmark suite. We don't cover this aspect at all.
I would be interested in real code that has non-trivial EH that is not
bound by the EH delivery performance, but to also see how much the EH
code slows down the non-EH paths.
I can think of something right now, but it's going to take a bit of time
to make it easily exploitable for this task. I put this on my list.
There is also another motivation related to exceptions/stack-unwinding
efficiency : some people are thinking that C++ might benefit from a
mechanism to perform non-destructive stack-unwinding, in order to be
able to access a variable higher-up on the stack without having to pass
it at each function call (a kind of "exceptional argument").
I have not seen any proposal for this yet (I might write one :-) ),
but this is certainly something that sounds like useful in the future,
and this might require "reasonnable" speed, and might increase the
number of places where we need to go over the stack efficiently,
and deal with types while doing so. Imagine something like :
void f()
{
T& t = get_context<T>();
}
void g()
{
T my_stack_context;
try_with (my_stack_context) {
f();
}
}
Then the function f accesses its stack context of type T, which has been
set higher-up in the call stack, in function g.
(I'm just scratching this as I write the email)
--
Sylvain Pion
INRIA Sophia-Antipolis
Geometrica Project-Team
CGAL, http://cgal.org/