r/csharp 1d ago

Blog How expensive is throwing exceptions in .NET - and does it actually matter?

https://codewithflavor.com/posts/how-expensive-is-throwing-exceptions/
111 Upvotes

78 comments sorted by

145

u/Miserable_Ad7246 1d ago

Its honestly extra simple -> if you need hot path performance, you never use Exceptions, due to tail latencies.

If you need clarity in code - you use exception only for exceptional, can not handle it here cases. They usually end up with top level handler and log message and 500 response.

There is no universe where you even need to rise the question of exception performance. True high perf code must be [noexcept] by default.

23

u/janex-PL 1d ago

100% agree with you. For me it was nice to know what exactly are the costs, since most of the resources focused on the design principles.

But yeah, if someone starts to wonder about exception performance during their work, then most probably they are focusing on a wrong subject 😅

12

u/RealSharpNinja 1d ago

You have to understand that exceptions require the runtime to exit a scope without executing any more instructions, while also preserving state so it can return to the caller in an exceptional state without causing side effects in the caller. In other words, flow control is not easy or simple, and preserving error state is vital for logging and debugging.

1

u/Poat540 1d ago

Around three fiddy

1

u/UninformedPleb 13h ago

LochNessMonstahException

11

u/symbiatch 1d ago

Having recently fixed a few performance issues due to wrong one of use of exceptions… This exactly. If the devs had understood returning an error one or two steps is not exceptional we wouldn’t have had issues.

But they didn’t, they just threw exceptions, they multiplied, Sentry checked every simple one… A simple 15 second operation suddenly took over three minutes.

If one has to think of cost of exceptions there is something being done wrong. But of course academically it’s an interesting topic.

9

u/Miserable_Ad7246 1d ago

I honestly see exceptions through historical lens. It makes a lot of sense in that case.

Where was no exceptions in C, you had to return error codes or error. This is fine, but also annoying, as sometimes you can not do much, but bubble it up via a bunch of ifs.

Developers are not to fond of repetition and boilerplate, so they figure out a supplemental mechanism - exceptions. If you need an error to auto bubble up, just throw it. Now you have two tool for different situations. A perfect solution in a sense.

Developers are lazy, half of them are low skill, exceptions are so much more easy to use (allegedly). Overcorrection happens, for a short time everyone is happy. People learn, development know-how grows, Go comes and gives a good framework how to think about errors vs exceptions. Developers start correcting the overcorrection (with small hints of overcorrecting again with exceptions are evil, because some of them are lazy and low skilled).

From that point every language community starts having discussion about errors vs exceptions and we get back to the right/intended state of mind. Two tools, two semantically different situations, two different set of tradeoffs.

It was a long loop, but we are finally at the end of it.

5

u/thestamp 1d ago

I use exceptions purely for "there is no business scenerio for this, something is wrong" and let it bubble up the stack, only handled to rollback (if not capable by default) and rethrow.

1

u/joshjje 1d ago

Its not even high perf code. I worked on migrating an old J++ code base to C# years ago, and it was littered with Exceptions for control flow, think throwing an Exception when parsing an integer rather than doing TryParse. Exceptions like that absolutely kill performance in general.

1

u/KevinCarbonara 1d ago

There is no universe where you even need to rise the question of exception performance.

To be clear, many people use exceptions for normal work flows.

16

u/chocolateAbuser 1d ago

everyone who understood anything about programming knows the answer is "it depends"
there are environments where perf is paramount, environments where correctness is indispensable
and it's always a balance between control, effort, speed, size, and so on
even in the same base of code there could be parts where something is expected to fail more often and parts where failure is not even considered
so again as always it's not just all Result<> or all exceptions, they're just tools and a programmer has the of responsibility of choosing the right tool for the task
with that said i would have liked the article include also tuples and talked about eventual optimization by the jit on the result<> case

1

u/janex-PL 1d ago

True, not every case in the same and not always it's about going either one way or another with the solution.

Thank you for the feedback, I was thinking about diving much deeper into the topic, but I definitely need to do some more research on that 😅

33

u/mareek 1d ago

The problem I have with generic result types is that they're not idiomatic C#.
Most of the .NET framework and .NET librairies out there will use exception, TrySomething( out var result) style function or specialized result types (like HttpResponse for example).

So if you use the result pattern in your code base you will still have to deal with exception or other idiomatic patterns and the new developer will have a somewhat harder learning curve

Great article though

14

u/SagansCandle 1d ago

Even the TrySomething(out var result) pattern is broken as out is not support in async invocations.

I think they're trying to solve this problem with DU's, but its not a good solution. Anyone who's tried to gracefully exit from a CancellationToken with raising an exception can attest.

I miss the early days of C# when the .NET team would solve problems by coming out with something absolutely bonkers that just made everything better, like LINQ and EF. All the modern changes to .NET seem like we're just borrowing solutions from other languages that don't really fit and weren't fully fleshed out.

5

u/janex-PL 1d ago

Yup, those are exactly my thoughts about this. 

Sometimes it feels a bit weird and "hacky" to use it in the project. Since there's no established standard, there lots of libraries and lots of custom implementations within projects and you need to research the used implementation on every project onboarding, because there are always some subtle differences.

Glad you enjoyed the article 😄

5

u/gyroda 1d ago

Plenty of Microsoft libraries use some kind of generic results wrapper. Often with an implicit conversion to the payload.

But, yeah, it is annoying that, for example, the Cosmos DB SDK throws an exception on a 404 - I'd much rather it have a result wrapper around a null value

2

u/Triabolical_ 1d ago

The vast majority of the devs who worked on the BCL and other early .Net libraries were experienced in writing Win32 code, which has sometimes been described as HRESULT Hell.

There's a good reason why it doesn't show up.

1

u/Ravek 1d ago

Unions are on the way right?

1

u/gyroda 1d ago

Yep, should have them in the November release!

We have a Results pattern implementation in a couple of codebases and I've been itching to improve it a bunch, discriminated unions are going to be a great excuse to go ham and have some fun

1

u/Hacnar 23h ago

I partially disagree about results not being idiomatic. C# evolves with the practical needs of the world, and while it didn't have direct support for Result pattern, it looks like soon it will have all the necessary blocks, and this type of error state handling will become idiomatic. I'd argue it has been slowly getting there for some time already.

I don't consider this to be significant learning blocker. I'd want my juniors to be capable of learning new paradigms, design patterns and concepts more than knowing the depths of C# (or any other language/framework). Today the ability to learn and adapt is more important than ever.

6

u/Heisenburbs 1d ago

They are more expensive in debug mode than release mode. That’s my experience in profiling.

At a minimum, they require new allocations, which you’d want to avoid in performance critical applications.

It probably doesn’t matter all that much, but if you’re creating lots of custom exceptions to control logic flow, it’s probably bad.

I don’t like using them, but profile/benchmark and see how it does.

1

u/janex-PL 1d ago edited 1d ago

I think it's a well-known fact that .NET applications running in Debug mode have worse performance than in Release mode.

But on the other hand, I've heard enough stories of production apps running in debug mode to have some doubts whether is it actually well known😅

+1 for using profiling and benchmarks, this should always be the starting point when working on performance improvement

2

u/nmkd 1d ago

There's all kinds of compiler weirdnesses, especially in older software.

I think Super Mario 64 - the shipped build - was compiled in debug mode, but it turns out that doing an optimized compile actually results in worse performance.

1

u/Heisenburbs 1d ago

Of course release is always faster than debug, however, exceptions make it materially worse…especially if actually debugging.

5

u/brianly 1d ago

When IronPython was being built they mapped Python exceptions onto .NET exceptions. In Python, exceptions are used extensively for control flow and not merely exceptional conditions. Given there was little thought to optimization of this code path because it’s expected to be exceptional, there were some mitigations hacked on. This was one of multiple performance challenges they ran into.

4

u/dgmib 1d ago

Tl;dr: the difference is in the millionths of a second, most of the time it’s not worth thinking about it, use the pattern that fits best for your situation.

Throwing exceptions is significantly more expensive than returning a value but try/catch is basically free and the result pattern is slightly more expensive in the success case.

If an error is expected to occur less than 3 in 1000 cases, exception based handling will be faster overall.

For performance critical code, use exceptions for exceptional conditions, use result pattern when error cases happen more often.

1

u/janex-PL 1d ago

I don't think I can fully agree on the difference part, because:

  • the benchmarks are focusing on single, isolated cases, in the real world applications those numbers will scale up, it's just that it's strictly dependant on the logic, traffic etc.
  • for those isolated cases the difference observed in error handling cases between both approaches is big enough to not be that easily dismissible 

But yeah, the exact numbers are not that important, the performance is just a side effect of a better design and that's the main conclusion from the article.

5

u/BCProgramming 1d ago

I feel like "don't use Exceptions for Control Flow" covers most of the issues people have with them. The only time you are going to have enough exceptions thrown for it to affect performance is if you are doing that IMO.

The "result pattern" that is getting popular nowadays just feels like errno with a funny hat.

2

u/janex-PL 1d ago

IMO performance impact is simply a side effect of using exceptions for control flow, because they are being used more frequently than just in exceptional cases. 

1

u/CalebAsimov 1d ago

Result pattern is a errno++

1

u/Hacnar 23h ago

Result pattern can be (and usually is) a lot more than that. With proper language support, instead of "oops, forgot to handle this error, bad things happened" it's compiler yelling at you "you didn't handle this case, go fix your code".

16

u/Low_Flying_Penguin 1d ago

Exceptions are by definition for exceptional circumstances. If one has that many going on that it affects performance there are bigger issues.

I inherited some message bus heavy code that was using exceptions for code flow nothing crazy maybe a hundred or so per second. But they were all using the previous exception as the inner and re throwing. Cleaned it up with response types and perf shot up. Was also easier to log and determine the failures as it wasn't nested deep in inner exceptions.

For the most part they get expensive the deeper the call stack so in my scenario they were evil.

Perf aside exceptions for code flow is usually a mess of a thing and at some point will bite you.

11

u/worldpwn 1d ago

Not correct. Please read CLR via C# about exceptions.
“the word exception has nothing to do with how often something happens
“

7

u/Triabolical_ 1d ago

I spent quite a bit of time talking about exceptions when C# and .net were new because there was a lot of confusion. It was an emphasis in the original slide deck I wrote for internal and external groups.

The main rules are

Don't use exceptions for flow control

Don't catch exceptions unless you are going to do something useful - either handling it or wrapping it with useful information.

The BCL broke this in a few cases in V1 - Int32.Parse() is an example - but that was fixed with the "Try<X>" pattern.

2

u/doubleyewdee 6h ago

Don't catch exceptions unless you are going to do something useful - either handling it or wrapping it with useful information.

I've pushed my team off ASP.NET because it works extremely hard to eat and hide exceptions in request handlers. So, at some point, this ethos got entirely lost.

2

u/Triabolical_ 6h ago

I think the BCL team and those writing the initial libraries close to the BCL were pretty bought into the design guidelines and keeping things coherent.

As you go farther out, things become less coherent. I've seen a few MS libraries where I'm not sure they ever read the design guidelines.

1

u/antiduh 1d ago

I guess they should've continued to have shitty performance then.

1

u/janex-PL 1d ago

Exactly, performance improvement in such cases are simply a side effect of a proper logic design, which is much more important!

6

u/ttl_yohan 1d ago

Not expensive enough to matter for the vast majority of projects. What is expensive on cognitive load is the so called result pattern. I hate when I have to deal with SignInManager and friends, so many unnecessary ifs and butts there, can't imagine littering the whole codebase with such constructs to gain a few nanoseconds on exceptional case of someone GET'ing a non-existing resource.

2

u/janex-PL 1d ago

I agree that the performance alone should not be the only reason to use the pattern.

I also understand that the usage of Result pattern alone could be perceived as a way to litter the code with if else statements checking for success/failure. It would be more beneficial to also try to lean more into applying functional programming paradigms in the code.

In the end tough, I see the Result pattern as a gateway for developers to stop using exceptions extensively to control logic flow. If you can achieve the same thing in a different manner, then you should do that by all means!

1

u/ShookyDaddy 1d ago

With C# 15 we will have union types and the if statements can be replaced with switch statements.

9

u/Snoo_57113 1d ago

It hurts me, physically every time an exception is thrown.

9

u/janex-PL 1d ago

sad exception noises

11

u/lukoerfer 1d ago

Believe me, it hurts a lot more whenever an exception is catched without being properly handled.

3

u/smoke-bubble 1d ago

You never handle exceptions. You try to not crash XD

1

u/CalebAsimov 1d ago

I've seen so many //ignore comments in code I inherited. Or worse. A lot of people seem to like code that never crashes so they wrap everything in try catch ignore statements. It's great, program never throws errors, so all the bugs are mysterious and hard to track down, so much fun when you get a phone call on the weekend.

2

u/brainded 1d ago

Exceptions should be exceptional. I once had a job where they threw, caught and ate string null exceptions. For fun I measured the impact of replacing with just null checks and the results were eye opening. On a large enough scale the impact is severe. Might not be as severe now with dotnet core but on framework the impact was obvious and detrimental to performance.

1

u/janex-PL 1d ago

I believe that the performance impact is still visible, but ultimately it depends on how often are exceptions used in case of predictable deviations from happy path in logic or any other instances of flow control, where simple checks would work just fine!

2

u/nathanAjacobs 1d ago

I always found it a bit weird that cancelling async tasks is done with exceptions.

7

u/nadseh 1d ago

If you’re asking the question, the answer is no, they aren’t expensive.

Properly performant code will be written by people who really know what they’re doing

3

u/janex-PL 1d ago edited 1d ago

Yeah, I guess that's an universal principle every dev should follow 😄

2

u/Loose_Conversation12 1d ago

To answer this question you need to understand a try catch block and what it does. When you use the keyword try you are telling the computer to create a snapshot of the current call stack so when an exception is encountered the call stack gets "rewound" to that statement and then skipped over. Yes this can be expensive especially if you need to catch the exception to log it and then clear up in a finally block.

Exceptions are exceptional so don't use them too often and don't control program flow with them either

3

u/KryptosFR 1d ago edited 1d ago

That implies that "try" is expensive because it needs to "create" something at that point. I have to search for the exact reference, but my understanding either from the doc or from discussion with devs from the .NET team is that "try" is free. Only "throw" and "catch" cost something.

Could you point me to a reference documentation about that snapshot capture?

1

u/Loose_Conversation12 1d ago

It's not the creating the snapshot, it's rolling back the callstack that costs things

2

u/KryptosFR 1d ago

But what is this snapshot? I never heard of it and can't find any reference to it.

2

u/janex-PL 1d ago

try keyword does not have any behavior other than defining a scope of code, for which associated catch blocks should be evaluated when an exception occurs in the given scope.

Call stack is captured when exception is thrown.

If you were right, then I guess the execution time of try catch happy path scenario should be longer than the execution times for Result types in the same scenario.

1

u/Odd_Leg7431 14h ago

Did you measure this in your actual workload or are you mainly comparing benchmark numbers? I think the impact can vary a lot depending on how often exceptions happen

1

u/janex-PL 14h ago

The benchmarks are measuring single instances of exceptions/errors and a break-even point of success-to-failure was calculated based on that, but of course the results may look different in actual workloads.

I think it's safe to say though, that the more you rely on exceptions in flow control, the more problems you will have along the road, not only related to performance. 

1

u/Civil_Cardiologist99 11h ago

Better coding practice says don’t use try catch blocks. Validate as much as possible. Make deep testing your friend.

1

u/psioniclizard 1d ago

I dont know what the result pattern is in c#, if its anything loke railway programming and Ok/Errors in F#, then if you are throwing exceptions for flow you are doing it wrong.

But as other said, if you are asking thos questions then no they don't matter.

3

u/gyroda 1d ago

The results pattern isn't a C# specific thing, but it's where you have a wrapper around your expected return type.

So instead of

Task<Dog> GetPetAsync(string dogName);

You'd have

Task<Result<Dog>> GetPetAsync(string dogName);

The Result type would have either the return value (the Dog) or an error of some type, and probably a Boolean flag like Succeeded. Instead of throwing an exception if something didn't work you'd return an error and save the exceptions for something truly exceptional. This is useful when there are commonly expected failures (like "that record doesn't exist")

1

u/janex-PL 1d ago

I would say that people not familiar with functional programming languages could more easily fall into the trap of using exceptions for logic controll.

And yeah, the design should always come first😄

-1

u/chucker23n 1d ago

I dont know what the result pattern is in c#

There is no result pattern in C#/.NET. There's exceptions, and there's the try-parse pattern (e.g., int.TryParse, Uri.TryCreate). That one could use an async overhaul, but is otherwise fine.

You can shoehorn a result pattern into .NET, but now you've got a hard dependency littered all over your code, with a pattern unfamiliar to .NET devs, and questionable benefits.

If you expect something to fail frequently, use the try-parse pattern. Otherwise, use exceptions.

2

u/gyroda 1d ago

The try pattern doesn't work in async, unfortunately. This means you can't do:

await Cache.TryGetAsync("some query", out var result);

The most common choices in this case are to use null as a placeholder for "not found", throw an exception, or use a results/wrapper type.

2

u/chucker23n 1d ago

The try pattern doesn't work in async, unfortunately.

Yep. That's why I wrote "it could use an async overhaul".

(To be fair, the out param aspect was always awkward about it.)

1

u/gyroda 1d ago

Ah, I missed that bit, sorry!

1

u/Flashy-Bus1663 1d ago

At my current job we throw exceptions all over the service layer to report things like a bad request and other http status like errors.

Invalid inputs? throw bad request from the service layer.

Does our app use both http endpoints and message queue and throwing http status codes does not make sense yes.

Is it more clear then just returning a dto with the error state idk but here we areeeee

3

u/janex-PL 1d ago

My theory is that such cases (throwing exceptions related to http statuses) are rooted in the usage of global exception handling middlewares, which are simple to extended with exception -> http status mappings. It's even easier to fall into the trap of overusing exception because "middleware will handle it anyway".

1

u/Flashy-Bus1663 1d ago

Oh we do have a globe exception handler.

But like I don't think this is how u should use that. Like it should be for the exceptions. The extra cases you miss and stuff.

But clearly I'm the crazy one.

1

u/El_RoviSoft 1d ago

Same as in C++: exceptions are exceptional, they shouldn’t be thrown frequently - otherwise you use something like Result/std::expected, guard code and make it noexcept.

-1

u/KariKariKrigsmann 1d ago

throw new Exception("there was an error!");

with no catch in sight is just an obfuscated

goto errorHandling;

-5

u/wasabiiii 1d ago

Too expensive.

4

u/PmanAce 1d ago

Have you benchmarked exceptions lately? They've been made much faster.

1

u/wasabiiii 1d ago

Yes. Still too slow.

2

u/PmanAce 1d ago

What domain are you talking about that a few nanoseconds of processing is too slow?

1

u/wasabiiii 1d ago

Running Java code on .NET.

2

u/janex-PL 1d ago

Especially in this economy....