Live data from Hacker News

Python 3.11: “Zero cost” exception handling

bugs.python.org

21–30 of 96 posts

Re: Python 3.11: “Zero cost” exception handling

#21
The tenacity of people getting excited over micro optimizations in Python for more than two decades is remarkable. Nothing has happened despite monumental speed programs that were broadly advertised and marketed to corporations.

Meanwhile, SBCL has an industrial strength compiler that predates Python and its trademark (the SBCL compiler was called "Python" before the trademark, thereby invalidating it). Python (the language) is mainly good at aggressive marketing.

Re: Python 3.11: “Zero cost” exception handling

#22
post #2

They weren't zero cost before? In a language where idiomatic control flow uses exceptions? That's crazy! I've felt weird using exceptions like that but I always assumed that CPython was optimized to minimize overhead of exceptions and exception handlers.

Exceptions always have a cost. What happened with many C++ runtimes is that they moved the execution cost of exceptions almost entirely into the exception raising mechanism so that the path of execution that does not raise exceptions has no overhead due to exceptions. This was not the case with the Python runtime.

The "zero cost" implementation for C++ exceptions in the case of ELF binaries means storing a bunch of static (compile-time generated) data regarding CPU register state changes for each function in a table indexed by function address offset within the DSO. When a "throw" expression is executed, the execution stack is crawled and that table evaluated for each function encountered until an appropriate "landing pad" (eg. a matching "catch" clause) is found, and then the stack is unwound by interpreting the function table contents to step through the various CPU states in reverse order, executing destructors as required. It's just like sufficiently advanced technology. In the case of no "throw" expression, there is no extra CPU cycles spent. This trades off execution time for extra memory storage in the no-exception case and more CPU cycles in the exception case.

As I understand it, the Python "compiler" just generates a bunch of straight-line code and decision statements so that when a function returns, its result is checked and if the result is that an exception was thrown it jumps to the local handling code otherwise it jumps to the local non-exception code. The price for handling exceptions is always paid all the way up the stack for every function call made.

Because exceptions are a fundamental control-flow mechanism in Python (unlike in C++, where they should only be used for exceptional control flow), I'm not sure if there will be a net benefit to "zero cost" exceptions. I guess they should try it and measure the difference under various scenarios and make an informed decision based on evidence.

Re: Python 3.11: “Zero cost” exception handling

#23
post #4

Earlier quoted context omitted.

The first that came to mind is how `get()` is handled in Django's ORM. The idiomatic way to look for a single object is to use `get`, then catch a `DoesNotExist` exception: From https://docs.djangoproject.com/en/3.2/ref/models/querysets/#... from django.core.exceptions import ObjectDoesNotExist try: blog = Blog.objects.get(id=1) entry = Entry.objects.get(blog=blog, entry_number=1) except ObjectDoesNotExist: print("Ei…

Right, but the better way to actually write this is something like entry = Entry.objects.filter(blog__id=1, entry_number=1).first() if entry is None: # deal with does not exist Maybe it's my scala/Java background shining through, but we are big Django users and we ban the "catch exceptions as standard" workflow, because there is almost always a cleaner way...

This fails to raise an error if there is more than one object matching the given filters

Re: Python 3.11: “Zero cost” exception handling

#24
post #4

Earlier quoted context omitted.

The first that came to mind is how `get()` is handled in Django's ORM. The idiomatic way to look for a single object is to use `get`, then catch a `DoesNotExist` exception: From https://docs.djangoproject.com/en/3.2/ref/models/querysets/#... from django.core.exceptions import ObjectDoesNotExist try: blog = Blog.objects.get(id=1) entry = Entry.objects.get(blog=blog, entry_number=1) except ObjectDoesNotExist: print("Ei…

Right, but the better way to actually write this is something like entry = Entry.objects.filter(blog__id=1, entry_number=1).first() if entry is None: # deal with does not exist Maybe it's my scala/Java background shining through, but we are big Django users and we ban the "catch exceptions as standard" workflow, because there is almost always a cleaner way...

What are you suggesting is better about that way? More readable?

Re: Python 3.11: “Zero cost” exception handling

#25
post #20

Earlier quoted context omitted.

On a smaller scale, it is.

To me sending wrong arguments is an error condition, not control flow, see this for more info: https://softwareengineering.stackexchange.com/questions/1892...

It's not about sending the wrong arguments. In Python a function may support a variety of types in a single argument. But instead of querying the type and switching based on that, you often want to try to call a method and if you get an exception (because that method does not exist), try to call a different method that gets you what you need.

The advantage to this over checking the type is that you are still use duck typing. If you check the type then you can only support a specific set of classes.

Re: Python 3.11: “Zero cost” exception handling

#26
post #2

They weren't zero cost before? In a language where idiomatic control flow uses exceptions? That's crazy! I've felt weird using exceptions like that but I always assumed that CPython was optimized to minimize overhead of exceptions and exception handlers.

This is on a different dimension.

Up to this point, in Python, exceptions were always checked, so they were "free" in the sense you were always paying for them anyhow. I remember many C++ programmers migrating to Python back in the ol' comp.lang.python days having this described to them. You don't have to worry about exception-heavy code, because since you're always paying for them anyhow, you might as well use them.

A lot of making CPython code run well works like that. You want to use as much of the stuff you're already always paying for anyhow, rather than reimplementing any of it in your own code.

Re: Python 3.11: “Zero cost” exception handling

#27
post #22
post #2

They weren't zero cost before? In a language where idiomatic control flow uses exceptions? That's crazy! I've felt weird using exceptions like that but I always assumed that CPython was optimized to minimize overhead of exceptions and exception handlers.

Exceptions always have a cost. What happened with many C++ runtimes is that they moved the execution cost of exceptions almost entirely into the exception raising mechanism so that the path of execution that does not raise exceptions has no overhead due to exceptions. This was not the case with the Python runtime. The "zero cost" implementation for C++ exceptions in the case of ELF binaries means storing a bunch of s…

This was really informative, thanks! Also “it’s just like sufficiently advanced technology” made my morning.

Re: Python 3.11: “Zero cost” exception handling

#28
post #25
post #20

Earlier quoted context omitted.

To me sending wrong arguments is an error condition, not control flow, see this for more info: https://softwareengineering.stackexchange.com/questions/1892...

It's not about sending the wrong arguments. In Python a function may support a variety of types in a single argument. But instead of querying the type and switching based on that, you often want to try to call a method and if you get an exception (because that method does not exist), try to call a different method that gets you what you need. The advantage to this over checking the type is that you are still use duck…

This is also known in Python as the "ask for forgiveness not permission" idiom.

Re: Python 3.11: “Zero cost” exception handling

#29
post #25
post #20

Earlier quoted context omitted.

To me sending wrong arguments is an error condition, not control flow, see this for more info: https://softwareengineering.stackexchange.com/questions/1892...

It's not about sending the wrong arguments. In Python a function may support a variety of types in a single argument. But instead of querying the type and switching based on that, you often want to try to call a method and if you get an exception (because that method does not exist), try to call a different method that gets you what you need. The advantage to this over checking the type is that you are still use duck…

Thanks for the clarification, I have actually seen this now that I think of it and this indeed is closer to control flow than error flow.

Re: Python 3.11: “Zero cost” exception handling

#30
post #22
post #2

They weren't zero cost before? In a language where idiomatic control flow uses exceptions? That's crazy! I've felt weird using exceptions like that but I always assumed that CPython was optimized to minimize overhead of exceptions and exception handlers.

Exceptions always have a cost. What happened with many C++ runtimes is that they moved the execution cost of exceptions almost entirely into the exception raising mechanism so that the path of execution that does not raise exceptions has no overhead due to exceptions. This was not the case with the Python runtime. The "zero cost" implementation for C++ exceptions in the case of ELF binaries means storing a bunch of s…

Note that the generated instructions - even if they are never run as in [1] - still consume cache and might hinder further optimizations. Which is why `noexcept` is becoming more popular. And because there is no GC, code must be written to be exception-safe in all conditions which is often forgotten.

1: https://godbolt.org/z/bKfG14P64 - the difference between `e()` and `n()` is that one is marked `noexcept`. Both `f()` and `g()` run directly to `ret` (return) in the normal case.

Post reply on HN