4 ms·
For working with arrays elements without bound checks, this is the modern alternative to pointers, without object pinning for GC and "unsafe" keyword: MemoryMar
by buybackoff 1y ago
For working with arrays elements without bound checks, this is the modern alternative to pointers, without object pinning for GC and "unsafe" keyword: MemoryMarshal. GetArrayDataReference<T>(T[]). This is still totally unsafe, but is "modern safer unsafe" that works with `ref`s and makes friends with System.Runtime.CompileServices.Unfafe.
Funny point: the verbosity of this method and SRCS.Unsafe ones make them look slower vs pointers at subconscious level for me, but they are as fast if not faster to juggle with knifes in C#.
The `fixed` keyword is mostly for fast transient pinning of data. Raw pointers from `fixed` remain handy in some cases, e.g. for alignment when working with AVX, but even this can be done with `ref`s, which can reference an already pinned array from Pinned Object Heap or native memory. Most APIs accept `ref`s and GC continues tracking underlying objects.
See the subtle difference here for common misuse of fixed to get array data pointer: https://sharplab.io/#v2:C4LghgzgtgPgAgJgIwFgBQcDMACR2DC2A3utmbjgK4B2EYAZgKa4As2AYpMABQCW1wbBF4AvRgEpipcjIBuYAE7ZFSgLzZqjAO7Z+wANrCxAXWwBuaTLL1eAD0YATPgIBU2AA7A1yhQskB6f2wAZTAoZkhsAFlGKAB7BQBPKMUIAAswABsAOgBxRmAAQV8wRIARMGAwACVGJgVGagBjZksrIgBfbDaOtrasbBo6JlYQzLitZ0EjCSk0K3J5JRVsdU0dPUNRRlMLeYXsG3snPTdPbwAyFX0ABmMAoODgXkzM7AAhfEdsYDjsAHMCj80sx3HE9IwFG0ZJ1uvsyL00B0gA= https://sharplab.io/#v2:C4LghgzgtgPgAgJgIwFgBQcDMACR2DC2A3ut...
Spans are great, but sometimes raw `ref`s are a better fit for a task, to get the last bits of performance.
- bengarney 1y agoI increasingly wonder if writing and binding performance critical things in C/C++ would be less overall effort. Performant zero-alloc C# vs C/C++ is backdoor magic vs first class language support. Boxing gloves vs. surgical gloves. C# _can_ do this! But I face many abstractions: special perf APIs, C#, IL, asm. Outcomes will vary with language version, runtime version, platform, IL2CPP/Burst/Mono/dotnet. But C/C++ has one layer of abstraction (the compiler), and it's locked in once I compile it. I want to do the thing as exactly and consistently as possible in the simplest way possible! A build environment that compiles .cpp alongside .cs (no automatic bindings, just compilation) would be so nice for this. ---- Example of what I mean regarding abstractions: void addBatch(int *a, int *b, int count) { for(int i=0; i<count; i++) a[i] += b[i]; } versus: [MethodImpl(MethodImplOptions.AggressiveOptimization)] public static void AddBatch(int[] a, int[] b, int count) { ref int ra = ref MemoryMarshal.GetArrayDataReference(a); ref int rb = ref MemoryMarshal.GetArrayDataReference(b); for (nint i = 0, n = (nint)count; i < n; i++) Unsafe.Add(ref ra, i) += Unsafe.Add(ref rb, i); } (This is obviously a contrived example, my point is to show the kinds of idioms at play.)
- int_19h 1y agoBut your first code snippet is also valid C# if you just throw in `unsafe` there. And, generally speaking, everything that you can do in C (not C++) can be done in C# with roughly the same verbosity.
- bengarney 1y agoIt is, but it isn't quite the same as C, either. That is to say, there is some semi-unknowable stack of stuff happening under the covers. I will predict the future: you will pull up the JIT assembly output to make the case that they output similarly performant assembly on your preferred platform, and that you just have to do X to make sure that the code behaves that way. But my problem is that we are invoking the JIT in the conversation at all. The mental model for any code like this inevitably involves a big complex set of interacting systems and assumptions. Failure to respect them results in crashes or unexpected performance roadblocks.
- int_19h 1y agoI don't see what makes JIT any different from AOT in this case. But C# can be AOT-compiled as well. Will it be as efficient? Probably not; C++ compilers have been in the optimization game for a very long time and have gotten crazy good at it. Not to mention that the language itself is defined in a way that essentially mandates a highly optimizing compiler to get decent performance out of it (and avoid unnecessary creation of temporaries and lots of calls to very tiny functions), which then puts pressure on implementations. But my point is that this is not a question of language, but implementation. Again, your C example is literally, token-for-token, valid C# as well. And, in general, you can take any random C program and mechanically convert it to C# with the exact same semantics and mostly the same look (with minor variations like the need to use stackalloc for local arrays). So if it's all 1:1, equivalent perf is certainly achievable, and indeed I'd expect a C# AOT compiler to do exactly the same thing as the C compiler here, especially if both are using the same backend; e.g. LLVM. Now in practice the implementations are what they are, and so even if you are writing C# code "C-style", it's likely to be marginally slower because optimizer is not as good. But the question then becomes whether it's "good enough", and in many cases the answer is "yes" - by writing low-level C# you already get the 90% perf boost compared to high-level code, and rewriting that in C so that it can be compiled with a more optimizing compiler will net you maybe 10% for a lot more effort needed to then integrate the pieces.
- EgorBo 1y ago"moder safer unsafe". It's actually quite the opposite most of the time, void* is safer as it's often doesn't involve GC. I recommend reading https://learn.microsoft.com/en-us/dotnet/standard/unsafe-code/best-practices https://learn.microsoft.com/en-us/dotnet/standard/unsafe-cod...
- buybackoff 1y agoI have mostly GC holes in mind when say "safer". Or heap fragmentation, even if it's POH