3 ms·
this whole post is kinda confusing to me. I know barely anything about c# under the hood. the only language I've ever used in performance sensitive use cases is
by leetcrew 3y ago
this whole post is kinda confusing to me. I know barely anything about c# under the hood. the only language I've ever used in performance sensitive use cases is c++.
to me, all of the benchmark code looks like an obvious opportunity for dead store elimination. especially this loop in the second round:
for (var i = 0; i < classes.Count(); i++)
{
var x = classes.ElementAt(i).Name;
}
it doesn't look like any side effects are possible there and x is never referenced outside of the loop. can anyone ELI5 why the compiler generates any code for that snippet?
- idonttalkenough 3y agohttps://stackoverflow.com/questions/3202464/garbage-collection-of-the-object-created-in-infinite-loop https://stackoverflow.com/questions/3202464/garbage-collecti... Extrapolating it to a larger sense, this SO thread explains it a little bit. The GC since the top answer has changed quite a bit; I'd read about the changes to the .net clr/framework since v5/6
- leetcrew 3y agointeresting thread, but doesn't really answer my question. as a human, it is obvious from that snippet alone that the variable x is never read. therefore, unless `classes.ElementAt(i).Name` has some side effect, the entire loop could be replaced with a no-op without changing the program semantics. so my question isn't about GC behavior at all. I expect the entire loop to be optimized away (no code emitted). why doesn't c# make this optimization? is it something subtle about how properties work, or does the compiler not attempt these optimizations in general?
- jkulubya 3y agoProperty accessors `.name` and methods `ElementAt(i)` can have side-effects. From just eyeballing the code, that together with the gc issue would make the compiler in my brain wary of removing the loop. I don't personally know if it's possible to convince the C# compiler that it's safe.
- to11mtm 3y ago... I could be wrong, but I -thought- it was possible for the JIT to see there are no side effects and inline on property access (so long as the property is not a virtual call or otherwise able to devirt.) .ElementAt() OTOH has a chance to throw in most implementations AFAIR so yeah, a bit of a moot point in this case. Edited to add: Actually, there -could- still be side effects in the case of .Name on a class, specifically, it's possible that .ElementAt() could return a null. I'm not sure what cases (if any) that the JIT could get around that. OTOH, in the case of a struct, as long as .ElementAt() -doesn't- throw, .Name will always return regardless of if it is a property or field, and as part of a struct the compiler should do a good job of inlining as long as the access has no side effects (and you don't have too many fields on the struct!)
- mumblemumble 3y agoJust taking a stab at it; this might not be exactly right -- It's kind of down to C# being compiled into bytecode and then JIT compiled at run-time. During the initial compilation phase, the compiler doesn't necessarily have enough information to know whether `ElementAt()` or `Name` has side effects. (I assume here that Name is a property getter and not a field, in keeping with .NET conventions.) And then at run time the JIT compiler isn't as aggressive as an AOT compiler would typically be about optimization, so it may be less likely to do any dead store elimination.
- idonttalkenough 3y agoPretty much correct from a historical sense. On top of this recent advancements in .net have lead to native AOT. Something to look into.
- idonttalkenough 3y agoNow a days the major performance difference between languages is memory handling and allocation. Microsoft has been making major moves in how c# gets compiled (AOT/JIT/Native). This is a concurrent effort to cross-platform support. In doing going so they've minimized performance differences with other languages. The only thing they've yet to completely tackle is memory handling, so in reality, while it might not seem like it at first, your question is asking about that subject. Also some small tidbits with property accessors that the other comments have noted. To my knowledge these will be optimized away soon. The GC is responsible for allocation and destruction.
- to11mtm 3y ago> Now a days the major performance difference between languages is memory handling and allocation. And thread management. > The only thing they've yet to completely tackle is memory handling, And thread management. Async/Await did a whole lot to help with concurrency, IValueTaskSource and IThreadPoolWorkItem helped bring the allocation cost for that back down... But I still don't have a good way to, say, hint to the scheduler that 'these async work loops are important enough that I want them to always run in this dedicated group of threads'. Also, having a way to get high precision Sleep() without hacks that have impact on the rest of the system would be nice too.
- tubthumper8 3y agoSimilar to javac, the C# compiler barely does any optimizations at all ahead of time. Optimizations are generally done at runtime by the JIT compiler in the CLR virtual machine
- nyssos 3y ago> it doesn't look like any side effects are possible there Putting side effects in an `ElementAt` implementation would be an extremely bad idea, but C# won't actually stop you.
- rozab 3y agoAs the author says, the list of names is actually read from file every single iteration because `classes` is a LINQ query. So there's all sorts of potential exceptions etc
- kevingadd 3y agoIt's impossible to guarantee that count and elementat have no side effects unless you fully devirtualize everything, which can't be done at compile time since the application's dependencies (SDK/runtime, third party libraries) could be swapped out before it runs. So these optimizations would have to occur in the JIT and might come at the cost of worse startup time or memory usage. Fwiw modern .net is getting pretty good at devirt but I don't expect it would optimize all this out.