[Cython] CF and finally clause

Stefan Behnel stefan_ml at behnel.de
Wed May 25 07:21:26 CEST 2011


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.html
>
> 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


More information about the cython-devel mailing list