Hacker News new | ask | show | jobs
by graboid 469 days ago
I sometimes write C# in my day job. But I think I don't know much about how to write really fast C#. Do you have any recommendations for learning resources on that topic?
3 comments

Sure. Here are some resources:

* Span<T>: https://learn.microsoft.com/en-us/archive/msdn-magazine/2018...

* C# now has a limited borrow checker-like mechanism to safely handle local references: https://em-tg.github.io/csborrow/

* Here is a series of articles on the topic: https://www.stevejgordon.co.uk/writing-high-performance-csha...

* In general, avoid enterprise style C# (ie., lots of class and design patterns) and features like LINQ which allocate a lot of temporaries.

LINQ is fine (but enterprise style never is, yes), it’s a matter of scale and what kind of a domain the code is targeted too. C# needs to be approached a little like C++ and Rust in this regard. Having standard performance optimization knowledge helps greatly.

Also can recommend reading all the performance improvements blog posts by Stephen Toub as well as learning to understand disassembly at a basic level which .NET offers a few convenient tools to get access to.

And I can recommend listening to @neonsunset when it comes to C# performance :)

Helped me a bunch to get Sharpl spinning, much appreciated.

https://github.com/codr7/sharpl

Thank you. I once read a bit about Span<T>, but some of this reference stuff is very new to me. Interesting, definitely. C# really is a big language nowadays...
Spans are just a slice type, but those which any type based on contiguous memory can be coerced to (usually). I’m sure you’re already using them somewhere without realizing that. Their main use case in regular code is zero-cost slicing e.g. text.AsSpan(2..8).
C# is specifically designed for enterprise-style OOP, so if you want to avoid that, why use C# at all?
You're thinking of Java, which is Enterprize Buzzword Compliant to the maximum extent possible.

C# is Java-but-with-lessons-learnt, and is significantly less verbose and "enterprisey" in typical usage.

Modern .NET 9 especially embraces compile-time code generation, a "minimal" style, and relatively high performance code compared to Java.

Even if the JVM is faster in benchmarks for hot loops, typical Java code has far more ceremony and overhead compared to typical C# code.

> Even if the JVM is faster in benchmarks for hot loops, typical Java code has far more ceremony and overhead compared to typical C# code.

Can you give an example? I don't think this is true anymore for modern Java (Java 21+)

It's a heavily gamed benchmark, but TechEmpower Fortunes is pretty good at revealing the max throughput of a language runtime for "specially tuned" code (instead of idiomatic code).

Java currently beats .NET by about 40%: https://www.techempower.com/benchmarks/#hw=ph&test=fortune&s...

I judge more idiomatic / typical code complexity by the length of stack traces in production web app crashes. Enterprise Java apps can produce monstrous traces that are tens of pages long.

ASP.NET Core 9 is a bit worse than ASP.NET Web Forms used to be because of the increased flexibility and async capability, but it's still nowhere near as bad as a typical Java app.

In terms of code length / abstraction nonsense overhead, have a look at the new Minimal APIs for how lightweight code can get in modern C# web apps: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/m...

For those interested in performance ceiling, https://benchmarksgame-team.pages.debian.net/benchmarksgame/... provides additional data points.

What matters in practical scenarios is that ASP.NET Core is significantly faster than Spring Boot. If you have a team willing to use ActiveJ or Vert.x, you are just as likely have a team willing to customize their C# implementation to produce numbers just as good at web application tasks and much better at something lower level. There are also issues with TechEmpower that make it highly sensitive to specific HW/Kernel/Libraries combination in ways which alter the rankings significantly. .NET team hosts a farm to do their own TechEmpower runs and it just keeps regressing with each new version of Linux kernel (for all entries), despite CPU% going down and throughput improving in separate more isolated ASP.NET Core evaluations. Mind you, the architecture of ASP.NET Core + Kestrel, in my opinion, leaves some performance on the table, and I think Techempower is a decent demonstration of where you can expect the average framework performance to sit at once you start looking at specific popular options most teams use.

How's modern Java in a game/sim scenario? C# has value types to reduce the GC load, for example. Do Java records close the gap there?
No, records are a reduction in boilerplate for regular classes (the result also happens to be read-only — not deeply immutable, mind you). Value types are in the works:

https://openjdk.org/jeps/401

> C# is specifically designed for enterprise-style OOP

Then why would they add Span<T>, SIMD types and overhaul ref types in the first place?

Because some people wanted to use C# for low-level programming, so they added these things as an afterthought.
You’ve clearly never used it and have no idea what you are talking about.
I have used it a few years ago and the enforced OOP boilerplate was too much for me.
Span<T>, ReadOnlySpan<T>, Memory<T>, CollectionsMarshal, CollectionsExtensions, ref struct, ref return, ArrayPool, ArraySegment, ValueTuple, and using interfaces/structs/generics carefully.

That is if you don't want to get into unsafe code.

A few important ones: - Avoid memory allocations as much as you can. That's a primary thing. For example, case insensitive string comparisons using "a.ToUpper() == b.ToUpper()" in a tight loop are a performance disaster, when "string.Equals(a, b, StringComparison.CurrentCultureIgnoreCase)" is readily available. - Do not use string concatenation (which allocates), instead prefer StringBuilder, - Generally remember than any string operation (such as extracting a substring) means allocation of a new string. Instead use methods that return Span over the original string, in case of mystr.Substring(4,6) it can be a.AsSpan(4,6), - Beware of some combinations of Linq methods, such as "collection.Where(condition).First()" is faster than "collection.First(condition)" etc.

Apart from that (which simply concerns strings, as they're the great source of performance issues, all generic best practices, applicable to any language, should be followed.

There are plenty resources on the net, just search for it.