4 ms·
I always felt like these features are adding a new programming paradigm to C# that allows you to bypass GC in safe code. I wish more people would talk about it
by zigzag312 2y ago
I always felt like these features are adding a new programming paradigm to C# that allows you to bypass GC in safe code.
I wish more people would talk about it. Thank you for such an interesting article!
- pjmlp 2y agoMany of these features exist since .NET 1.0, given its scope of languages to support, including C++. So even those that weren't initially exposed in unsafe mode, were available at the MSIL level and could be generated via helper methods making use of "System.Reflection.Emit". Naturally having them as C# language features is more ergonomic and safer than a misuse of MSIL opcodes.
- zigzag312 2y ago"System.Reflection.Emit" is not compatible with NativeAOT. Using C/C++/Rust to do the same task is probably more productive than emitting MSIL opcodes, so that solution wasn't really that practical. But with these new features being more ergonomic and practical, it becomes cost effective to just do it in C# instead of introducing another language.
- deleted 2y ago[deleted]
- pjmlp 2y agoYeah, but nothing of that is the point being discussed, with Native AOT still not available in GUI workloads. Also P/Invoke and CCW/RCW do have costs cross the runtime layer, even if minor when compared with other languages.
- zigzag312 2y agoI'm not sure I follow. Where are GUI workloads being discussed in the article? If anything, article doesn't talk about MSIL or CLR, but C# language features. CLR is not the only target C# supports. NativeAOT is supported in Avalonia (cross-platform UI framework), Razor Slices (dynamically render HTML from Minimal APIs) and I think there is also some support for AOT in MonoGame & FNA (game dev frameworks). However, it's still early and a lot of the ecosystem doesn't support NativeAOT.
- pjmlp 2y agoNo, neither was Native AOT. Native AOT depends on CLR infrastructure.
- Rohansi 2y agoI believe you can avoid most of not all of the P/invoke overhead these days by using unmanaged function pointers and not using the automatic marshalling.
- neonsunset 2y agoWhenever you use [DllImport], the analyzer will nudge you to auto-fix it to [LibraryImport] which source-generates a marshalling stub (if any is needed) that then calls an inner [DllImport] that does not require runtime marshalling. This is very cheap since function address gets cached into a readonly static which then gets baked into the machine code once the JIT produces Tier-1 compilation for your method. On NativeAOT, you can instead use "DirectPInvoke" which links against specified binary and relies on system loader just like C/C++ code would. Then, you can also statically link and embed the dependency into your binary (if .lib/.a is available) instead which will turn pinvokes into direct calls (marshalling if applicable and GC frame transition remain, on that read below). Lastly, it is beneficial to annotate short-lived PInvoke calls with [SuppressGCTransition] which avoids some deoptimizations and GC frame transition calls around interop and makes the calls as cheap as direct calls in C + GC poll (a single usually not-taken branch). With this the cost of interop effectively evaporates which is one of the features that makes .NET as a relatively high-level runtime so good at systems programming. Unmanaged function pointers have similar overhead, and identical if you apply [SuppressGCTransition] to them in the same way. * LibraryImport is not needed if pinvoke signature only has primitives, structs that satisfy 'unmanaged' constraint or raw pointers since no marshalling is required for these.
- zigzag312 2y agoSaving this as I don't remember seeing such succinct explanation of these attributes before :)
- MarkSweep 2y agoThere is a runtime (not C#) feature that has been added that is relevant to the article: ref fields in structs. Before these, only certain runtime-blessed types like Span<T> could contain refs directly. In case anyone is interested, here is the spec about refs in structs and other lifetime features mentioned in the article: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md https://github.com/dotnet/csharplang/blob/main/proposals/csh... And here is the big list of ways .NET differs from the publish ECMA spec. Some of these differences represent new runtime features. https://github.com/dotnet/runtime/blob/main/docs/design/specs/Ecma-335-Augments.md https://github.com/dotnet/runtime/blob/main/docs/design/spec...
- moomin 2y agoAnd the latest version is massively relaxing the ref restrictions on generics.
- algorithmsRcool 2y agoSpan and ref-like types enable massive changes to the way that memory is managed in C#. You can absolutely write almost GC-less code. I have been tinkering with a toy no-GC database engine in C# based on Direct I/O and some object pooling. I have been amazed at how far i can get before resorting to GC heap allocations
- DeathArrow 2y agoAnything that uses classes and interfaces will be memory managed by the GC. So instead of using lists, dictionaries, IEnumerable, you will have to roll your own. It would be better if the GC can be turned off with a switch and just add a delete operator to manually free memory.
- neonsunset 2y agoGiven that ref structs can now be generic arguments and cannot be boxed - you have more ways to enforce that no boxing occurs at compile-time. It is true that you have to roll your own collections, but even dispatching on interfaces by making them generic constraints (which is zero-cost) instead of boxing is a good start. As for delete operator, 'dispose' works well enough. I have a toy native vector that I use for all sorts of one-off tasks: // A is a shorthand for default allocator, a thin wrapper on top of malloc/realloc/free // this allows for Zig-style allocator specialization using var nums = (NVec<int, A>)[1, 2, 3, 4]; nums.Add(5); ... // underlying pointer is freed at the end of the scope It is very easy to implement and I assume C and C++ developers would feel right at home, except with better UX. This retains full compatibility with the standard library through interfaces and being convertible to Span<T>, which almost everything accepts nowadays. System-provided allocators are slower at small allocations than GC, but Jemalloc easily fixes that.
- DeathArrow 2y agoI really mean using existing stuff, without rolling your own: List<int> nums = [1, 2, 3, 4]; //do stuff with nums Delete(nums);
- pjmlp 2y agoThis kind of features is C# catching up to what was already possible in languages like Modula-3. Unfortunately, as usual in computing, we have to do huge circles shaped in zig-zag, instead of adopting what was right in front of us.
- johnisgood 2y agoYeah I feel like we are taking "reinventing the wheel" to a whole new level, and with enough time, people forget, same with Lisp and Forth (i.e. how they just re--implement stuff that were already a thing in those two languages, but perhaps under a different name).
- jason_oster 2y agoAs completely off topic as my response will be, I'll at least keep to the theme of this thread. I was reminded the other day of an article that called out React and Flux for reinventing the Windows 1.0 dispatch architecture, and it made me laugh: https://www.bitquabit.com/post/the-more-things-change/ https://www.bitquabit.com/post/the-more-things-change/ Also, can't miss the opportunity to bring up Graydon's iconic 2010 talk "Technology from the past come to save the future from itself". http://venge.net/graydon/talks/ http://venge.net/graydon/talks/
- CrimsonCape 2y agoThanks for the article. Hopefully your laugh wasn't a cynical laugh... It seems more relevant than ever to study the fundamental discoveries made in the early history of Windows. We don't know the magnitude of how much the rendering API affects the success of an operating system. It would be safe for a novel OS to heed the importance of the rendering API. But more abstractly, it could be that the best novel OS competitor to Windows is simply the open-source flavor. It would be stronger evidence to see someone building Windows 1.0 in a modern sense, stronger than any other evidence, that a serious competitor is incoming. The next OS won't be written in Javascript (sorry React).
- uticus 2y ago