Hi! When I create control flow graph I have to visit finally clause twice. This is required because of different outputs should be generated for success and exception case: try: a = might_raise() finally: pass # 'a' might be uninitialized here print(a) # and definitely defined here So after tracking both cases I have to merge states back because same code is generated for each. Maybe it's a good idea to split success/exception case by copying it at PostParse transform? -- vitja.
On Tue, May 24, 2011 at 12:33 PM, Vitja Makarov <vitja.makarov@gmail.com> wrote:
Hi!
When I create control flow graph I have to visit finally clause twice. This is required because of different outputs should be generated for success and exception case:
try: a = might_raise() finally: pass # 'a' might be uninitialized here print(a) # and definitely defined here
So after tracking both cases I have to merge states back because same code is generated for each.
Maybe it's a good idea to split success/exception case by copying it at PostParse transform?
-1 I don't see any difference compared to merging the states from if b: a = None else: # don't assign to a - Robert
Robert Bradshaw, 24.05.2011 22:07:
On Tue, May 24, 2011 at 12:33 PM, Vitja Makarov wrote:
When I create control flow graph I have to visit finally clause twice. This is required because of different outputs should be generated for success and exception case:
try: a = might_raise() finally: pass # 'a' might be uninitialized here print(a) # and definitely defined here
So after tracking both cases I have to merge states back because same code is generated for each.
Note that there are also the break, continue and return cases.
Maybe it's a good idea to split success/exception case by copying it at PostParse transform?
-1
I don't see any difference compared to merging the states from
if b: a = None else: # don't assign to a
The difference is that try-finally executes the exact same code in both cases, but both cases are otherwise in completely distinct code paths. In a way, it's the opposite of your example, where the same outer code path can execute either of the distinct inner code sections. I'm not so opposed to this proposal. I have been (idly and unfoundedly) wondering basically forever if the current way try-finally is implemented is actually a good one. I can accept a performance penalty for the exception case in both try-finally and try-except, but the normal case in try-finally should run as fast as possible. I'm not sure the C compiler can always detect what that "normal" case is in order to properly optimise for it. However, given that there are actually up to four normal cases and only one exception case, I also wonder if even Cython can detect what the "normal" case should be, and if it's really worth providing a distinct implementation in order to optimise it. Stefan
On Tue, May 24, 2011 at 2:04 PM, Stefan Behnel <stefan_ml@behnel.de> wrote:
I'm not so opposed to this proposal. I have been (idly and unfoundedly) wondering basically forever if the current way try-finally is implemented is actually a good one. I can accept a performance penalty for the exception case in both try-finally and try-except, but the normal case in try-finally should run as fast as possible. I'm not sure the C compiler can always detect what that "normal" case is in order to properly optimise for it.
Evidently Java compilers duplicate the finally block (or, actually, triplicate it): http://cliffhacks.blogspot.com/2008/02/java-6-tryfinally-compilation-without... Carl
On Tue, May 24, 2011 at 2:17 PM, Carl Witty <carl.witty@gmail.com> wrote:
On Tue, May 24, 2011 at 2:04 PM, Stefan Behnel <stefan_ml@behnel.de> wrote:
I'm not so opposed to this proposal. I have been (idly and unfoundedly) wondering basically forever if the current way try-finally is implemented is actually a good one. I can accept a performance penalty for the exception case in both try-finally and try-except, but the normal case in try-finally should run as fast as possible. I'm not sure the C compiler can always detect what that "normal" case is in order to properly optimise for it.
Evidently Java compilers duplicate the finally block (or, actually, triplicate it):
http://cliffhacks.blogspot.com/2008/02/java-6-tryfinally-compilation-without...
Interesting... I don't like the idea of copying code all over, Stefan makes some good points. - Robert
Robert Bradshaw, 25.05.2011 01:30:
On Tue, May 24, 2011 at 2:17 PM, Carl Witty wrote:
On Tue, May 24, 2011 at 2:04 PM, Stefan Behnel wrote:
I'm not so opposed to this proposal. I have been (idly and unfoundedly) wondering basically forever if the current way try-finally is implemented is actually a good one. I can accept a performance penalty for the exception case in both try-finally and try-except, but the normal case in try-finally should run as fast as possible. I'm not sure the C compiler can always detect what that "normal" case is in order to properly optimise for it.
Evidently Java compilers duplicate the finally block (or, actually, triplicate it):
http://cliffhacks.blogspot.com/2008/02/java-6-tryfinally-compilation-without...
Interesting...
I don't like the idea of copying code all over, Stefan makes some good points.
Note that we generate slightly different code for the good and bad cases anyway. Only the exception case stores away and restores the exception, the other ones don't need to do that. I also dislike code duplication in general, but finally clauses tend to be really short. Most of the time, it's just a couple of lines of cleanup code, often just a single function/method call ("file.close()"). In a Cython context, it's even better because many of the finally clauses will just do C cleanup ("free()"), without major Python operations that would bloat the generated code with C-API calls or optimistic code paths. For example, I can't remember having ever seen for-loops or tuple unpacking in a finally clause, which are the things (apart from Python argument unpacking) that Cython generates the longest code for. All that a finally clause really gives you is to make sure the body gets started. If it raises an exception itself, you're on your own again. So I'd rather expect to find try-except(-pass) inside of a finally clause than a nested try-finally, which makes recursive code explosion rather unlikely. I just looked through lxml's sources and found that out of 36 finally clauses, 26 (more than 2/3) are one-liners like "thing.close()", "free(mem)" or "self.attr = None". Only two clauses are longer than three lines because they do different things in Py2 and Py3 (so the C compiler will drop part of it), everything else is basically just a permutation of the above three statements or an if-test before cleanup. A quick skip through the stdlib seems to second that: lots of one liners, some if-tests, very few other cases. Longer blocks are truly rare, such as in the dummy threading module, which has a lengthy "if+cleanup" finally clause at the module scope, or the subprocess module, which has one 4-step "if+cleanup" section (obviously in the Windows code ;) ). IMHO, not really worth bothering about. I think that programmers tend to be rather aware of what belongs into a finally clause and what doesn't really need to go there, either because they know that this will get executed in all possible cases, so it should only be the strict intersection of the different code paths - or simply because "finally" seems too mystical to entrust it with larger code blocks. Stefan
participants (4)
-
Carl Witty -
Robert Bradshaw -
Stefan Behnel -
Vitja Makarov