in ,

Investigating the Performance Overhead of C ++ Exceptions | Inside PSPDFKit, Hacker News

Investigating the Performance Overhead of C ++ Exceptions | Inside PSPDFKit, Hacker News

Runtime error handling is hugely important for many common operations we encounter in software development – from responding to user input, to dealing with a malformed network packet. An application shouldn’t crash because a user has loaded a PNG instead of a PDF, or if they disconnect the network cable while it’s fetching the latest version of PSPDFKit for Android . Users expect a program to gracefully handle errors, either quietly in the background, or with an actionable and user-friendly message.

Dealing with exceptions can be messy, complicated, and – crucially, for many C developers – slow. But as with so many things in software development, there is more than one way to cook an egg. In this blog post, we will be taking a closer look at C exceptions, what their downsides are, how they might affect the performance of your application, and what alternatives we have at our disposal to lessen their use.

The purpose of this post is not to deter us from using exceptions entirely; exceptions have their use, and in many cases, they are unavoidable. (Consider an error detected inside a constructor. How do we report that error?) Rather, this post is focused on using exceptions for general flow control, and it provides us with an alternative to help develop robust and easy-to-follow applications.

A Quick Benchmark Test

How much slower are exceptions in C compared to simpler flow control mechanisms?

Exceptions are much more complicated than a simple break or ) return , but just how much extra work does the processor have to do? Let’s put it to the test!

In the code below, we have a simple function that generates a random number and checks for a certain number before producing an error. The random number check ensures the computer has some work to do at runtime so that the compiler cannot simply optimize away our tests. Here are our test cases:

      Exit by throwing an int . Although not especially practical, this is the simplest exception we can do in C , and it is useful for stripping out as much complication as possible for this particular test. Exit by throwing an std :: runtime_error , which can contain a string message. This is much more practical than the int example. We’ll see if there’s much overhead in addition to the added complexity. Exit with a void return .

    1. Exit with a C-style int error code.
    2. The test is run using Google's lightweight benchmark library , which runs many cycles of each test. Eager readers may want to skip straight to the

    results .

      The Code

      Our super complex random number generator:

      And the test functions:

      Finally, we can integrate our tests with the Google benchmark library:

      For readers who wish to try it out for themselves, the full test code can be found here

    .

    The Results

    Below we have the output from the benchmark – first without any compiler optimizations, and then with - O2 .

    Debug -O0: Release -O2:

    (Run on (MacBook Pro 2.5GHz i7)

    The results are pretty staggering! We can see a huge gap in time taken to run the exception code vs. the basic return or error code approach. With compiler optimizations, there is an even greater difference.

    This is by no means a perfect test. The complier can probably do a lot of optimization with the code we have supplied in tests 3 and 4. Caveats aside, the differences are huge and the test demonstrates how much overhead we can expect to see with exceptions.

    Thanks to the zero-cost exception model used in most C implementations (see ) section 5.4 of TR 18015

    ), the code in a try block runs without any overhead. However, a catch block is orders of magnitude slower. In our simple example, we can see how slow throwing and catching an exception can be, even in a tiny call stack! Speed ​​will decrease linearly with the depth of the call stack, which is why it’s always best to catch an exception as close to the throw point as possible.

    So if exceptions are so slow, why do we use them?

    Why Use Exceptions?

    The benefits to exceptions are explained nice and succinctly in the Technical Report on C Performance (section 5.4) :

    The use of exceptions isolates the error handling code from the normal flow of program execution, and unlike the error code approach, it cannot be ignored or forgotten. Also, automatic destruction of stack objects when an exception is thrown renders a program less likely to leak memory or other resources. With exceptions, once a problem is identified, it cannot be ignored - failure to catch and handle an exception results in program termination.

    The key takeaway here is that an exception cannot be ignored or forgotten; if we have an exception, it must be dealt with. This makes exceptions extremely powerful built-in tools in C , and something that no simple C-style error code can replace. Exceptions are useful for situations that are out of the program’s control, e.g. the hard disk is full, or a mouse has chewed through your network cable. In such rare situations, an exception is an ideal and perfectly performant tool for the task.

    But what about all those errors inside the program’s control? If a function can produce an error, we want a mechanism where it is semantically obvious to the programmer that they need to check for an error, while also providing useful information about the error, if it occurs, in the form of a message or some other data (much like an exception).

    Expected

    We know that exceptions are really slow, and if you’re programming in C , you generally don’t want slow - especially for general flow control error handling. Exceptions are also hopelessly serial, meaning they must be dealt with immediately, and they do not allow for storing of an error to be handled at a later time.

    With the upsides and downsides in mind, what alternatives do we have at our disposal? Here at PSPDFKit, we use a class called Expected which is an idea originally proposed by Dr. Andrei Alexandrescu. His talk on Systematic Error Handling in C is excellent, and it's well worth a watch to help you understand the full power of this strategy.

    The following pseudocode gives an idea of ​​how Expected can look. Expected allows for either a T to be created or the exception that prevents T to be created. Simply put, it is a wrapper for a union of an expected return value and an error:

    The real-world implementation is a bit more complex that this; the talk mentioned above will fill in all the nitty-gritty implementation details.

    So the basic idea behind Expected is that a function that can produce an error has an expected return value of a certain type (an int , a class, void, and so on). If the function call succeeds, the return value is stored in an Expected instance and retrieved with the value () accessor. If something goes wrong, the error is stored in the same instance of Expected and retrieved with the error () accessor. Once the function has been called, it is simple to check whether we have the value or an error. If there is an error, there’s no need for a slow and “must-be-handled-immediately” catch block; Instead, we can check for hasError () and get the error message whenever it’s suitable.

    Speed ​​Test!

    Let's plug our Expected class into the test functions described above and see how it can be used:

    Drumroll please…

    Debug -O0: Release -O2:

    Not bad! We've managed to take our no-optimizations std :: runtime_error result from ns to a mere ns. The results are even more impressive with optimizations: (ns to) 5 ns. That's more than times faster with - O0 and well more than (times faster in with - O2 !

    So there we have it: Expected gives us a much faster and more versatile control flow error-handling mechanism than exceptions do. It is also semantically clear, and we don’t need to compromise on error messages.

    At PSPDFKit, we use this technique to speed up our code while failing quickly and gracefully when required - all the while retaining the ability to show our users descriptive and useful error messages from our APIs.

    (Conclusion)

    Exceptions aren’t all bad. In fact, they are extremely performant at what they are designed for: exceptional circumstances! We only start running into problems when we use them for general control flow, where much more efficient solutions are already available.

    The benchmark tests, albeit rather crude, showed us the huge gains in performance we can achieve by avoiding catching exceptions when a return will suffice.

    In this post, we also took a brief glimpse at the Expected class and how we can use this design to speed up our error handling. Expected allows us to be more flexible with when to handle errors, while also keeping the code flow easy to follow and retaining our descriptive messages for our users and programmers.

    (Read More )

What do you think?

Leave a Reply

Your email address will not be published. Required fields are marked *

GIPHY App Key not set. Please check settings

Where are all the animated SVGs ?, Hacker News

FTC ordered Amazon, Apple, Google, Facebook, Microsoft to disclose acquisition documents, Recode

FTC ordered Amazon, Apple, Google, Facebook, Microsoft to disclose acquisition documents, Recode