Good evening dear experts! I have a question: I need a method of the following form:

public void MyMethod(Func del) { for(int I = 0; I < 1000; I++) del(); } 

Actually, what's the problem: you need the del delegate to be called a number of times in the method (for example, a thousand as mentioned above), but the fact is that Func is a kind of “generic” type - in fact, it could be Func <T1> and Func <T1, T2> and Func <T1, T2, T3>. More th, T1, T2 and T3 can be int, float, double. In addition, I do not know how it would be possible to pass, in addition to the delegate, a set of its parameters with which it should be called. I wish I had the opportunity to do something like this:

 var del1 = new Func<int>(() => 10); var del2 = new Func<int, int, int>(i => i); var del3 = new Func<int, int, int>((i, j) => i + j); MyMethod(del1); MyMethod(del2, parametersForDel); MyMethod(del3, parametersForDel); 

Is it possible to somehow write some general method MyMethod, which any delegate of the specified type that interests me may receive as an argument? Or do you need to write your method for each Func? And what about the passing of parameters for these delegates? Thank you in advance

  • uh ... you want the stan ... it seems to me that you incorrectly interpret the meaning of delegation ... if you need your delegate to volunteer more than once, as intended, and then proxy, make a delegate to which the delegate is served and N is called Once and pass it ... but it is strange IMHO. - JEcho
  • my goal is not to call the delegate N times, this is just for example. The MyMethod method performs the same actions, but does it with different delegates. In order not to duplicate the code of the MyMethod method several times, I would like to be able to call it regardless of which delegate is sent to it - JuniorTwo
  • one
    Most likely, you are doing something wrong: you should not want such an idea. Describe better your real task, think about it. What you need based on the question is simply the void MyMethod(Action a) method, to which you can send different lambdas: MyMethod(d1) , MyMethod(() => d2(arg)) , MyMethod(() => d3(arg1, arg2)) , etc. - VladD
  • one
    the real problem is this - there are many similar methods of one of three types: Func <TRes>, Func <T, TRes> and Func <T, U, TRes>. It is necessary to compare these methods for the speed of their implementation. That is, I need a method that a delegate of any of these types could receive, call it many times and measure the speed of execution of this delegate. - JuniorTwo
  • @JuniorTwo: Oh, it's already warmer. And where are you going to get the parameters from? Is it the same time or new every time? - VladD

2 answers 2

Oh, performance testing! Testing is a rather difficult topic, it is very difficult to make it completely correct. It is believed that this is a good task for a beginner, but in fact the architect should have written the right performance tests.

Let's take a look at what we want to test.

First, running the method once is not a good thing. The execution of the method is influenced by hundreds of random parameters: for example, a random surge in the activity of the antivirus or a page of the code that is forced out to disk. Therefore, it is necessary to run the method many times: the results of a single run are statistically irrelevant. (Regarding how many times you need to run a test: do not take a number from the ceiling, but ask a mathematics colleague!)

Further, the test should be carried out with the same parameters ! Otherwise, our averaging does not make sense: we average incomparable values. If the function of one code branch is fast and the other is slow, it is meaningless to average the run time over them: you need to have two tests that test different branches.

Thus, we have to execute the same code many times: call a function with the same parameters. This means that our function call wraps around perfectly in Action , and you can use the same method to test different functions:

 class PerformanceTester { const int repetitions = 1000; // может быть, нужна внешняя параметризация static public TimeSpan ComputeAverageExecutionTime(Action a) { var executionTicks = new List<long>(); for (int i = 0; i < repetitions; i++) executionTicks.Add(MeasureTime(a)); double averageTicks = executionTicks.Average(); return new TimeSpan((long)averageTicks); } static private long MeasureTime(Action a) // in ticks { var stopwatch = new Stopwatch(); stopwatch.Start(); a(); stopwatch.Stop(); return stopwatch.ElapsedTicks; } } 

Use this:

 void f() { /* ... */ } void g(int arg) { /* ... */ } void h(ClassArg arg1, int arg2) { /* ... */ } ... var r1 = PerformanceTester.ComputeAverageExecutionTime(f); var r2 = PerformanceTester.ComputeAverageExecutionTime(() => g(1)); ClassArg arg1 = new ClassArg(); // не включаем конструктор в лямбду var r3 = PerformanceTester.ComputeAverageExecutionTime(() => h(arg1, 0)); 

But this is not the final decision. It has flaws that we will try to close.

First, JIT. The first run of the function will be slower due to the fact that at this moment it will be compiled by just-in-time-compiler. Plus the necessary parts of the code will be loaded into memory. So you need to include the "warming up code" in the test:

 static public TimeSpan ComputeAverageExecutionTime(Action a) { // разогреваем код: a(); var executionTicks = new List<long>(); for (int i = 0; i < repetitions; i++) executionTicks.Add(MeasureTime(a)); double averageTicks = executionTicks.Average(); return new TimeSpan((long)averageTicks); } 

Secondly, among a large number of runs, there will still be random "emissions" up and down. To level them, one must know the standard deviation σ, and reject results that differ from the estimated average by more than 3σ. But this requires advanced mathematics, and we can simply throw out the largest and smallest values; this is also a pretty good heuristic.

 var maxVal = executionTicks.Max(); if (executionTicks.Count(v => v == maxVal) < executionTicks.Count / 3) executionTicks.RemoveAll(v => v == maxVal); var minVal = executionTicks.Min(); if (executionTicks.Count(v => v == minVal) < executionTicks.Count / 3) executionTicks.RemoveAll(v => v == minVal); // здесь осталось не менее [[1000 * 2/3] * 2/3] = 444 элементов, // значит, список не пуст, и исключение не выбросится double averageTicks = executionTicks.Average(); 

Third, let's see what we measure. In addition to the function itself, we measure the time of its indirect call , via the lambda and the delegate. For most functions, this is irrelevant: the run time of a function is usually much longer than the call time for a delegate, so this subtlety can be neglected. Another thing, if your function is very small, for this you will have to write test code manually.

For example, such:

 class PerformanceTester { static public TimeSpan ComputeExecutionTimeForSmallFunctions( Action execute1000times, Action execute1time) { // разогреваемся execute1time(); var ticks = MeasureTime(execute1000times); return new TimeSpan((long)(ticks / 1000.0)); } // остальные функции } 

We use this:

 void q() { /* что-то очень быстрое */ } var r4 = PerformanceTester.ComputeExecutionTimeForSmallFunctions( () => { for (int i = 0; i < 1000; i++) q(); } q); 

Disadvantages: the indirect call of the delegate is still included in the measurement, but now the delegate is not called 1000, but only 1 time, and introduces only 1/1000 distortion. Fortunately, for a small function, you can safely raise the constant 1000 to large values, for example, to 1000000. At the same time, the results of random large deviations from the average with which we struggled for long functions by discarding the minimum and maximum values ​​are leveled. In addition, the cycle control time is added to the execution time of the function.

Fourth, do not forget that performance is highly dependent on the compiler settings. Therefore, never test the performance of a code compiled in DEBUG mode. In addition, the mileage in Visual Studio slows down the speed of the code approximately twice (!), Since the debugger specifically “asks” the JIT compiler to optimize less. Therefore, after debugging, run the performance tests only outside Visual Studio, and only compiled in RELEASE mode.

Fifth, do not forget about the presence of a pretty smart optimizer! It has the right to call a function call, and if you ignore the result, throw out the entire calculation! Therefore, in the case of ComputeExecutionTimeForSmallFunctions return value should somehow be used, at least stored in an array, added to a battery or something else.

    And if so?

     delegate void MyDelegate(params object[] values); public void MyMethod(MyDelegate del) { ... } 

    In general, such a need is questionable. Maybe you should reconsider the task?

    • why is it doubtful? - JuniorTwo
    • Well, I can’t think of a single task where a similar implementation would be needed. Wise somehow turns out. But this, again, is purely my opinion :) - Donil