The cost of C++ exception handling

DrMickeyLauer
Posts: 168
Joined: Sun May 22, 2022 2:42 pm

The cost of C++ exception handling

Postby DrMickeyLauer » Fri Dec 08, 2023 3:17 pm

I tried to stay away from C++ exception handling on all my ESP32 family work, since legend has it that it's a costly device, in particular on embedded systems.

These days though, I'm writing a driver for an SPI device where a lot of the code is consisting of error handling to check whether the SPI transfer has worked OK (and other edge cases), which is probably the case in 99.99%.

Since I'm writing in C++ anyways and the caller site is so much better if you can just return the payload instead of the error code, I wonder whether I should rather introduce C++ exception handling for that.

MicroController
Posts: 1708
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: The cost of C++ exception handling

Postby MicroController » Fri Dec 08, 2023 8:34 pm

I do use C++ exceptions, specifically for the reasons you mentioned, i.e. much more simple/cleaner code flow and a better API.
I identified three.5 'costs' of exceptions so far:
1) Binary size: The code the compiler will generate under the hood solely for propagating potential exceptions reportedly amounts to 5-8% of increased binary code size.
2) Stack space: Exceptions seem to require quite a bunch of stack memory if thrown. IIRC, when testing I had to increase my task's stack size by 2kb to accomodate exception handling compared to the same task when not provoking an exception.
3) RAM for the "emergency pool": Will be lost 'forever' for the application. I assume 1-2kb would be sufficient, but haven't tried.
3.5) CPU time iff an exception is actually thrown. On an ESP32C3, I timed a trivial try { throw myex {}; } catch (const myex& e) {...} and it clocked in at around 180000-200000 CPU cycles (i.e. over 1ms!) used between throwing and catching the exception.
This or may may not sound bad, but compared to if(result == NOT_GOOD) { handle_error(); } catching an exception is slower by a factor of about 10000.

As a middle-ground, I also use 'result' objects (with operator bool()) where it makes sense, i.e. Result_t r = try_it(); if(r) { r.useResultValue(); }.

DrMickeyLauer
Posts: 168
Joined: Sun May 22, 2022 2:42 pm

Re: The cost of C++ exception handling

Postby DrMickeyLauer » Sat Dec 09, 2023 2:33 pm

I also did some measurements on my ESP32S3.

It looks like I'm down to 416µs here (measured with triggering GPIOs and looking via Oscilloscope) for the "throw" case.

The "try/catch" contributes in the nanosecond region, so that's pretty zero cost.

Provided that I almost **never** throw, this will improve my call site a lot and I can live with the performance impact in the "throw" case.

Thanks a lot!

RandomInternetGuy
Posts: 52
Joined: Fri Aug 11, 2023 4:56 am

Re: The cost of C++ exception handling

Postby RandomInternetGuy » Tue Jan 02, 2024 4:29 am

IMO, this question has been well answered and the costs actually measured and considered instead of relying on folklore. Gold star.

As a reminder, especially to those coming from a background in C, you CAN have multiple values returned from a function. Sometimes that plays out really well and sometimes it just makes the call stack and argument convention terrible, but it IS a tool to keep handy in your toolbox. (In C you CAN return a struct, too...)

Code: Select all

auto foo_writer() {
  return std::make_tuple(error_code, bytes_written);
}

{
 ...
  auto [ error_code, bytes_written ] = foo_writer() ;
}
std::optional is another approach that works in some cases.

std::expected also lets you express some of these cases pretty well with a new syntax that, IMO, is even clearer. That example becomes something like

Code: Select all

std::expected<long, ErrorCode> foo_writer() {
   if (device_is_on_fire) {
     return std::unexpected { ErrorCode::DeviceCombusted }
   ...
   return bytes_written;
}

{
  auto io_result = foo_writer();
  if (io_result.has_value() {
    u.u_base += io_result.value();
  } else {
    u.u_error = io_result.error();
  }
}
Admittedly, with a simple byte count like this, it's easy to say "use a negative value" or "use zero" or whatever, but if the actual argument is a std::vector<iobuffs> where an iobuf is a pair of start, len pairs or whatever, it gets more funky to try to smuggle an error code along inline like that.


I haven't looked the extensa code for these, but the RISC-V code (they generally track as they're both very normal systems with generous register counts - like pretty much everything from the last few decades) returns those values - that are probably already in register up in foo_writer anyway - as $a0 and $a1 from the caller. If it gets inlined, the costs for all this just disappear.

It's way cheaper than unwinding a throw.


These hammers don't fit all nails and this isn't an open invitation for the "C89 is all we ever need" crowd to show up for a fight, but it's a reminder that you have choices beyond exceptions.

MicroController
Posts: 1708
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: The cost of C++ exception handling

Postby MicroController » Tue Jan 02, 2024 11:16 am

RandomInternetGuy wrote:
Tue Jan 02, 2024 4:29 am
IMO, this question has been well answered and the costs actually measured and considered instead of relying on folklore. Gold star.
That'd be neat. Do you have any links/figures you can share w.r.t. the ESP32s?
you have choices beyond exceptions.
Unless you consider being able to steer the (exceptional) control flow without an if/else after every call the real benefit of exceptions...

Who is online

Users browsing this forum: Andy Korg, biakss, Bing [Bot], Google [Bot], twompark and 84 guests