Delegate Group and Method Performance

Hi, I studied the performance hit when creating Cached dependency objects, so I wrote a very simple test program as follows:

using System; using System.Collections.Generic; using System.Diagnostics; using System.Web.Caching; namespace Test { internal class Program { private static readonly string[] keys = new[] {"Abc"}; private static readonly int MaxIteration = 10000000; private static void Main(string[] args) { Debug.Print("first set"); test7(); test6(); test5(); test4(); test3(); test2(); Debug.Print("second set"); test2(); test3(); test4(); test5(); test6(); test7(); } private static void test2() { DateTime start = DateTime.Now; var list = new List<CacheDependency>(); for (int i = 0; i < MaxIteration; i++) { list.Add(new CacheDependency(null, keys)); } Debug.Print("test2 Time: " + (DateTime.Now - start)); } private static void test3() { DateTime start = DateTime.Now; var list = new List<Func<CacheDependency>>(); for (int i = 0; i < MaxIteration; i++) { list.Add(() => new CacheDependency(null, keys)); } Debug.Print("test3 Time: " + (DateTime.Now - start)); } private static void test4() { var p = new Program(); DateTime start = DateTime.Now; var list = new List<Func<CacheDependency>>(); for (int i = 0; i < MaxIteration; i++) { list.Add(p.GetDep); } Debug.Print("test4 Time: " + (DateTime.Now - start)); } private static void test5() { var p = new Program(); DateTime start = DateTime.Now; var list = new List<Func<CacheDependency>>(); for (int i = 0; i < MaxIteration; i++) { list.Add(() => { return p.GetDep(); }); } Debug.Print("test5 Time: " + (DateTime.Now - start)); } private static void test6() { DateTime start = DateTime.Now; var list = new List<Func<CacheDependency>>(); for (int i = 0; i < MaxIteration; i++) { list.Add(GetDepSatic); } Debug.Print("test6 Time: " + (DateTime.Now - start)); } private static void test7() { DateTime start = DateTime.Now; var list = new List<Func<CacheDependency>>(); for (int i = 0; i < MaxIteration; i++) { list.Add(() => { return GetDepSatic(); }); } Debug.Print("test7 Time: " + (DateTime.Now - start)); } private CacheDependency GetDep() { return new CacheDependency(null, keys); } private static CacheDependency GetDepSatic() { return new CacheDependency(null, keys); } } } 

But I can not understand why this result is as follows:

 first set test7 Time: 00:00:00.4840277 test6 Time: 00:00:02.2041261 test5 Time: 00:00:00.1910109 test4 Time: 00:00:03.1401796 test3 Time: 00:00:00.1820105 test2 Time: 00:00:08.5394884 second set test2 Time: 00:00:07.7324423 test3 Time: 00:00:00.1830105 test4 Time: 00:00:02.3561347 test5 Time: 00:00:00.1750100 test6 Time: 00:00:03.2941884 test7 Time: 00:00:00.1850106 

In particular:

  • Why is test4 and test6 much slower than their delegate version? I also noticed that Resharper specifically has a comment on the delegate version suggesting change test5 and test7 - "Hidden Method Group". This is the same as test4 and test6 but are they actually slower?
  • It seems I don’t agree the performance difference when calling test4 and test6, shouldn't static calls always be faster?
+4
source share
4 answers

I didn't look too much into your code, but the first step would be to switch to using the StopWatch class instead of DateTime.Now, etc.

http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

+2
source

In tests with a group of methods (4.6), the C # compiler does not cache the delegation object (Func). Every time he creates a new one. In 7 and 5, it caches the Action object to a generated method that calls your methods. Thus, creating Funcs from method groups is very slow (coz from placing a Heap), but the call is quick because the action points directly to your method. And creating actions from lambdas is fast, since Func is cached, but points to the generated method, so there is one unnecessary method call.

Beware that not all lambdas can be cached (closures break this logic)

+2
source

This is pretty interesting. I am wondering if your million login lists do not cause garbage collection and distort your results. Try changing the calling order of these functions and see what the results give you.

Another thing is that JIT could optimize your code to create lambda every time and just insert the same value over and over again. It might be worth running ildasm over it and see what is actually generated.

+1
source

Why is test4 and test6 much slower than their delegate version? I also noticed that Resharper specifically comments on the delegate version, proposing to change test5 and test7 to "Covert to method group". What is the same as test4 and test6, but are they actually slower?

You will get a big key by adding

  Debug.Print(ReferenceEquals(list[0], list[1]) ? "same" : "different"); 

to the end of each method.

With the delegate version, Func gets the compiled bit, as it actually was:

 var func = Func<CacheDependency> <>_hiddenfieldwithinvalidC#name; if (func == null) { <>_hiddenfieldwithinvalidC#name = func = () => p.GetDep(); } 

While in the group of methods it compiles in the same way as:

 func = new Func<CacheDependency>(p.GetDep()); 

This memory has been done a lot with delegates created from lambdas, when the compiler can determine that it is safe, but not with groups of methods that are passed to delegates, and the performance differences that you see show exactly why.

I don't seem to agree with the performance difference when calling test4 and test6, aren't static calls always faster?

Not necessary. While a static call has the advantage of one smaller argument to pass through (since there is no implicit this argument), this difference:

  • It’s not worth starting.
  • May be disabled if this not used.
  • It can be optimized that the register with the this pointer before the call is the register with the this pointer after the call, so there is no need to actually do anything to get it.
  • Uh, something else. I am not saying that this list is exhaustive.

Indeed, the performance benefits of statics are more if you do things that are naturally static in instance methods, you may end up skipping objects that are really unnecessary and waste time. However, if you do what is a natural instance in static methods, you can end up storing / retrieving and / or distributing g and / or passing objects in arguments that you don't need and will be just as bad.

0
source

Source: https://habr.com/ru/post/1304157/


All Articles