8 ms·
In .NET, You're supposed to use reflection for discovery and then generate the code, at runtime, in a Assembly which is loaded and JIT'Ted by the runtime just l
by thepumpkin1979 10y ago
In .NET, You're supposed to use reflection for discovery and then generate the code, at runtime, in a Assembly which is loaded and JIT'Ted by the runtime just like any other library.
- taspeotis 10y agoWell I think the benchmarks show that there's quite a bit of nuance to it. Reflection generated code to get a property's value from its get accessor ends up 8.82x slower than what RyuJit does, because you're stuck with a method call rather than method inlining. ("GetViaProperty" is just reading from a field.) That doesn't mean you can't reflect even more and get the backing field and generate code to access it, but now you've got your optimizing compiler hat on! And even then, I think you still can't get 1:1 performance because you have to call the generated method vs. having it inlined.
- ori_b 10y agoWhy can't the jit inline the generated code?
- taspeotis 10y agoStatic dispatch vs. dynamic dispatch. Assuming the functions and types are known in advance, no virtuals etc: given something like `Main` calling `DoSomething` calling `GetPropertyValue` which is public int GetPropertyValue(MyObject myObject) { return myObject.PropertyValue; } When RyuJit compiles your application, it can see these methods and how they're called. `PropertyValue` can be inlined into a field access, `GetPropertyValue` can be inlined into `DoSomething` which in turn can be inlined into `Main`. When you do something like the article: Func<TestClass, string> getDelegate = (Func<TestClass, string>)Delegate.CreateDelegate( typeof(Func<TestClass, string>), property.GetGetMethod(nonPublic: true)); [Benchmark] public string GetViaDelegate() { return getDelegate(testClass); } RyuJit can't see that `getDelegate` is the equivalent of `return myObject.PropertyValue`. It's a reference to some function. RyuJit has to compile calling an arbitrary method vs. calling `GetPropertyValue`.
- kevingadd 10y agoFWIW, the pre-RyuJIT .NET runtime was definitely capable of inlining and devirtualization. I've seen it do the optimization for IDisposable.Dispose calls, off the top of my head. I would expect RyuJIT is capable of doing this in more places. I don't know if they've ever implemented this for delegates, though.
- taspeotis 10y agoYeah my wording is a bit loose. There's a lot of clever stuff that RyuJit etc. do for performance. I believe the JVM has even more clever stuff although I haven't dedicated much time to learning what it does better or differently.
- int_19h 10y agoIt was actually capable of inlining to the point where it could do some tricks that you'd normally expect only from C++. For example, if you use generics and structs rather than delegates to implement higher-order functions, like so: class Program { interface IFunc<T1, T2, TResult> { TResult Invoke(T1 x1, T2 x2); } struct AddInt32 : IFunc<int, int, int> { public int Invoke(int x, int y) { return x + y; } } static T FoldLeft<T, F>(T[] xs, F f) where F : IFunc<T, T, T> { var res = xs[0]; for (int i = 1; i < xs.Length; ++i) { res = f.Invoke(res, xs[i]); } return res; } static void Main() { Console.ReadKey(); int[] xs = { 1, 2, 3, 5, 8 }; int res = FoldLeft(xs, new AddInt32()); Console.WriteLine(res); } } I compiled and ran it with 3.5 SP1 x86 (the old 64-bit JIT wasn't good, and won't inline in this case). It didn't inline FoldLeft, but it did inline AddInt32 into the loop - this is from VS debugger disassembly: for (int i = 1; i < xs.Length; ++i) { 017B0106 mov edx,1 017B010B mov edi,dword ptr [ecx+4] 017B010E cmp edi,1 017B0111 jle 017B011E res = f.Invoke(res, xs[i]); 017B0113 mov eax,dword ptr [ecx+edx*4+8] 017B0117 add esi,eax for (int i = 1; i < xs.Length; ++i) { 017B0119 inc edx 017B011A cmp edi,edx 017B011C jg 017B0113 }
- deleted 10y ago[deleted]
- deleted 10y ago[deleted]
- velox_io 10y agoReflection is fine, just never use it inside a tight loop/ hot code path. I've written a database engine in C#/ .net and I quickly learnt what you can and can't use. Using the dynamic/ expando parts have insane memory costs. You have to be very careful what language features you use. If you want performance then static methods are your friend, as are Generics (sometimes sealed classes), arrays & vectors (you start to realise how important CPU cache is). Memory streams & pointers are sometimes needed, but gains are often less than you'd think. Visual Studio has some really good debugging tools, especially viewing the disassembly (hit a breakpoint then ctrl+alt+d) to see what is really happening. I love Linq, but it can be awfully slow. Lastly, when you run tests. Make sure you not only do multiple runs, but also pause in-between. I've seen the jit yield >20% improvements in micro-benchmarks I will blog about this stuff one day!
- eropple 10y ago> I will blog about this stuff one day! I'd love to read this blog post. One thing to keep in mind, for readers--while LINQ can be awfully slow, it often isn't and should be your first stop (and then improving on it when you find hot paths). I build game engines in C#, and I happily use LINQ to get something done--then I go back, profile my code (anything you're not getting out of your profiler is likely mythical and at best lying to you), and replace as-needed.
- velox_io 10y agoThanks! I guess I should have said that LINQ is a bit of a mine-field; it can generate some very arcane code/ expression trees from relatively simple predicates. I would have loved to have used it (would have saved me writing a query engine), there was simple too much performance overhead (especially memory usage).
- WorldMaker 10y agoOptimizing LINQ is a lot like optimizing Haskell (or to a lesser extent F#) and it isn't so much a minefield as it is dealing with that sort of abrupt paradigm-shift/learning curve wall of a mini embedded functional programming language. I don't think a lot of people understand exactly where some of the eagerness versus laziness boundaries are (which are essentially monad boundaries, from a functional perspective). For instance, I've suggested for some time [1] that one of the strongest things a UX (whether an IDE or an extension to an IDE) could do to help is better mark those boundaries between IQueryable/IEnumerable/IList, as I don't think a lot of people see them. (I've long considered ToList somewhat harmful, especially; it is so painfully rarely the right tool for any task and yet is so easy for people new to LINQ to use as a crutch.) Beyond that there is of course the general rules of optimizing any code: are you using the right data structure for the job and have you balanced caching the expensive operations (queries) against what isn't expensive and doesn't need to be cached. Certainly a lot of optimizations I've seen needed over the years are simply: this lookup would be faster in a Dictionary or a HashSet or an ILookup, instead of an O(n) search through an IEnumerable or IList. (This should be especially obvious any time that you attempt a `join` of any sort against an IEnumerable or IList on either side of the `join` that you need a Dictionary, HashSet, or ILookup on one or both sides.) That sort of data structure optimization is largely the same consideration when you are writing do/while/for/foreach loops directly as it is in the LINQ syntax and it can take a mental leap for developers sometimes to realize it that it is mostly the same problems, even if they sometimes "feel" different. [1] https://1drv.ms/p/s!AkQwwfqxgHzNxOsAj1KSVqxGaiydnQ https://1drv.ms/p/s!AkQwwfqxgHzNxOsAj1KSVqxGaiydnQ