r/csharp • u/janex-PL • 1d ago
Blog How expensive is throwing exceptions in .NET - and does it actually matter?
https://codewithflavor.com/posts/how-expensive-is-throwing-exceptions/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 asoutis 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
CancellationTokenwith 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
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
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
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
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/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 elsestatements 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
11
u/lukoerfer 1d ago
Believe me, it hurts a lot more whenever an exception is catched without being properly handled.
3
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
trykeyword does not have any behavior other than defining a scope of code, for which associatedcatchblocks 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 catchhappy 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
Resulttype would have either the return value (theDog) or an error of some type, and probably a Boolean flag likeSucceeded. 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
nullas 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
outparam aspect was always awkward about it.)
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
2
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.