Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does C# have zero-cost abstractions?

I normally program in languages that have the concept of zero-cost abstractions like C++ and Rust.

Currently I'm working in a project that uses C# language. So I was wondering if I can safely create abstractions and higher level code without compromising the performance.

Is that possible in C# or for performance critical code I should just do as low level code as possible?

Just as an example I encountered in my code (don't focus too much on this example, my question is more high level), I needed a function that would return multiple values, for that, my first approach was to use a tuple, so something like this:

public (int, int, float) Function();

or Abstract this tuple into a struct:

public struct Abstraction { int value1; int value2; float value3; };

public Abstraction Function();

What I expected is that the compiler would optimize the Tuple or the Abstraction struct away and simply use the primitive values directly. But what I found is that writing the code using out parameters would improve performance:

public void Function(out int value1, out int value2, out float value3);

I'm guessing the reason is because in the out function, there is no Tuple or Abstraction struct creation.

The problem with the out function version is that I really hate to use parameters as return values, since it seems more like a hack to a language limitation.

So, in the end I'm not sure if I'm just not using the correct configuration so the JIT could use zero-cost abstraction or this is simply not possible or not guaranteed in C#.

like image 444
Sassa Avatar asked Jan 30 '23 16:01

Sassa


1 Answers

First of all, I don't think it makes sense to say that languages "have zero-cost abstractions". Consider the abstraction of function. Is it zero-cost? Generally speaking, it is zero-cost only if it's inlined. And while C++ compilers tend to be really good about inlining functions, they don't inline all functions, so function in C++ is strictly speaking not a zero-cost abstraction. But this difference only matters rarely in practice, which is why you can usually consider a function to be zero-cost.

Now, modern C++ and Rust are designed and implemented in such a way that they make abstractions zero-cost as often as possible. Is this different in C#? Kind of. C# is not designed with such focus on zero-cost abstractions (e.g. invoking a lambda in C# always involves what's effectively a virtual call; invoking a lambda in C++ does not, which makes it much easier to make it zero-cost). Also, JIT compilers generally can't afford to spend as much time on optimizations like inlining and so they generate worse code for abstractions than C++ compilers. (Though this might change in the future, since .Net Core 2.1 introduced a tiered JIT, which means it has more time for optimizations.)

On the other hand, the JIT compiler is tweaked to work well for real code, not for microbenchmarks (which I assume is how you came to the conclusion that returning a struct has worse performance).

In my microbenchmark, using a struct indeed had worse performance, but it was because JIT decided not to inline that version of Function, it was not because of the cost of creating a struct, or anything like that. If I fixed that by using [MethodImpl(MethodImplOptions.AggressiveInlining)], both versions achieved the same performance.

So, returning a struct can be a zero-cost abstraction in C#. Though it's true that there is smaller chance of that happening in C# than in C++.

If you want to know what is the actual effect of switching between out parameters and returning a struct, I suggest you write a more realistic benchmark, not a microbenchmark, and see what the results are. (Assuming I got it right that you used a microbenchmark.)

like image 139
svick Avatar answered Feb 02 '23 11:02

svick