Ero curioso di sapere perché Lambdas e i delegati si sono esibiti così male nel test di Kristian Wedberg
Ho pensato che forse dovrebbero essere chiamati una volta prima che il test inizi a verificare che siano JIT, in quanto ciò potrebbe distorcere il tempo.
Nel mio test su .NET 4.6.1 e x86 il lambda esegue costantemente sia le chiamate di interfaccia che le chiamate, solo il delegato è stato più lento.
Ho quindi diviso il caso di test delegato in 3 per testare diversi modi di creare il delegato:
- allocazione da un gruppo di metodi (metodo istanza )
- allocazione da un gruppo di metodi (metodo statico )
- allocazione da un lambda
Risultati:
No function 1.404,5 MOps/s, 0,356 s
Non-virtual 429,2 MOps/s, 1,165 s
Static 432,9 MOps/s, 1,155 s
Virtual via class 427,7 MOps/s, 1,169 s
Overridden via class 399,4 MOps/s, 1,252 s
Base class 363,6 MOps/s, 1,375 s
Non-virtual via interface 331,3 MOps/s, 1,509 s
Virtual via interface 337,2 MOps/s, 1,483 s
Lambda 339,4 MOps/s, 1,473 s
Delegate (inst. mthd grp) 338,5 MOps/s, 1,477 s
Delegate (stat. mthd grp) 204,0 MOps/s, 2,451 s
Delegate (lambda) 341,5 MOps/s, 1,464 s
È interessante notare che 1) e 3) funzionano in modo coerente così come i test lambda o dell'interfaccia, solo 2) è più lento (questa era la versione usata nel test di Kristian). Sono sconcertato sul motivo per cui un metodo statico di tutte le cose dovrebbe essere più lento.
Ecco il test modificato:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
interface IClassA {
int Func1(int a);
int Func2(int a);
}
class ClassA : IClassA {
[MethodImpl(MethodImplOptions.NoInlining)]
public int Func1(int a) {
return a + 2;
}
public virtual int Func2(int a) {
return a + 2;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static int StaticFunc(int a) {
return a + 2;
}
}
class ClassB : ClassA {
public override int Func2(int a) {
return a + 2;
}
}
delegate int MyDelegate(int a);
class Program {
static int staticDelegate(int a) {
return a + 2;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static Func<int, int> GetFunc() {
return l => l + 2;
}
public static void Main() {
MethodCall();
}
public static void MethodCall() {
const int loops = 500000000;
int x = 0;
ClassA a = new ClassA();
ClassB b = new ClassB();
ClassA c = new ClassB();
Console.WriteLine("Method Call Overhead:");
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < loops; i++) {
x = x + 2;
}
watch.Stop();
Report("No function", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
watch.Restart();
for (int i = 0; i < loops; i++) {
x = a.Func1(x);
}
watch.Stop();
Report("Non-virtual", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
watch.Restart();
for (int i = 0; i < loops; i++) {
x = ClassA.StaticFunc(x);
}
watch.Stop();
Report("Static", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
watch.Restart();
for (int i = 0; i < loops; i++) {
x = a.Func2(x);
}
watch.Stop();
Report("Virtual via class", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
watch.Restart();
for (int i = 0; i < loops; i++) {
x = b.Func2(x);
}
watch.Stop();
Report("Overridden via class", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
watch.Restart();
for (int i = 0; i < loops; i++) {
x = c.Func2(x);
}
watch.Stop();
Report("Base class", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
IClassA iClassA = a;
watch.Restart();
for (int i = 0; i < loops; i++) {
x = iClassA.Func1(x);
}
watch.Stop();
Report("Non-virtual via interface", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
watch.Restart();
for (int i = 0; i < loops; i++) {
x = iClassA.Func2(x);
}
watch.Stop();
Report("Virtual via interface", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
Func<int, int> func = GetFunc();
x += func(0) - 2; // call once to JIT
watch.Restart();
for (int i = 0; i < loops; i++) {
x = func(x);
}
watch.Stop();
Report("Lambda", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
MyDelegate myDelegate;
myDelegate = a.Func1;
x += myDelegate(0) - 2; // call once to JIT
watch.Restart();
for (int i = 0; i < loops; i++) {
x = myDelegate(x);
}
watch.Stop();
Report("Delegate (inst. mthd grp)", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
myDelegate = Program.staticDelegate;
x += myDelegate(0) - 2; // call once to JIT
watch.Restart();
for (int i = 0; i < loops; i++) {
x = myDelegate(x);
}
watch.Stop();
Report("Delegate (stat. mthd grp)", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
myDelegate = new MyDelegate(i => i + 2);
x += myDelegate(0) - 2; // call once to JIT
watch.Restart();
for (int i = 0; i < loops; i++) {
x = myDelegate(x);
}
watch.Stop();
Report("Delegate (lambda)", loops, watch.ElapsedMilliseconds);
x -= 2 * loops;
Console.ReadKey();
Console.WriteLine(x); // so the compiler doesn't optimize it away
}
static void Report(string message, int iterations, long milliseconds) {
Console.WriteLine(string.Format("{0,-26:} {1,10:N1} MOps/s, {2,7:N3} s", message, (double)iterations / 1000.0 / milliseconds, milliseconds / 1000.0));
}
}