Lamba expressions e IL
Era da tempo che mi chiedevo come il compilatore gestisse una funzione lambda. Esistono diverse possibilità al riguardo, tra cui una generazione a Runtime tramite dynamic methods. Oggi ho messo mano al disassemblatore .NET, alla ricerca di una risposta concreta.IL? Questo sconosciuto...
Il funzionamento del .NET è solitamente quello di una macchina virtuale, molto simile ai concetti che fanno girare generazioni managed precedenti come quelle di Java. Il nostro codice, quando compilato con profilo non nativo, genera una serie di istruzioni di un linguaggio astratto comprensibile alla macchina virtuale, chiamato Intermediate Language e utilizzato in informatica per l'appunto per disgiungere completamente il livello hardware dal livello software e scrivere funzioni non necessariamente legate a una piattaforma ben definita. Le implementazioni della VM infatti decidono le infrastrutture su cui il codice può girare. Questa astrazione ha anche il vantaggio di permettere con buona approssimazione di livellare determinati comportamenti su tutte le generazioni di hardware che supportano la VM. Ad esempio, è possibile in linea di massima non tenere conto della dimensione degli indirizzamenti o dei registri della CPU. Dati complessi sono disponibili su tutte la VM che supportano l'IL, cambia praticamente solo la velocità di esecuzione, maggiore su hardware che supportano a livello fisico le funzionalità richieste.Nel .NET l'intermediate Language è chiamato CIL (Common Intermediate Language). La lista degli OP CODES dell'IL Microsoft è disponibile all'interno della classe System.Reflection.Emit.OpCodes e viene usata per la compilazione dei dynamic methods, argomento che prima o poi tratterò.
IL e Lambda, la storia non è difficile.
Per verificare il comportamento del compilatore in presenza di una lambda a parità di condizioni di esecuzione ho scritto un progetto dedicato esclusivamente a mettere in evidenza le differenze di codice tramite disassemblatore. Il principio era abbastanza banale: scrivere due classi che facessero la stessa e medesima cosa, ovvero lanciassero un thread, ma in una definendo il delegate void mediante lambda, mentre nell'altra usando la classica dichiarazione di metodo.Definisco "parità di condizioni di esecuzione" come la mancanza totale di violazioni dello scope delle variabili. Ovvero, variabili dichiarate nei metodi "padre" delle lambda non verranno utilizzate all'interno dei metodi figlio. Questa condizione richiederà un secondo test e la definizione di codice il più possibile oggettivo e confrontabile.
Invece, nei casi banali trattati in questo articolo il risultato anche se semplice è affascinante.
TaskWithLambda.cs
public class TaskWithLambda { public void Run(bool async) { Task task = null; task = new Task(() => { TimeSpan startTime; TimeSpan doubleStartTime; TimeSpan doubleEndTime; TimeSpan arrayStartTime; TimeSpan arrayEndTime; TimeSpan endTime; List<double> values = null; double[] source = null; double[] destination = null; Random rnd = null; StringBuilder testResult = null; int maxIteractions = 2500000; startTime = TimeSpan.FromTicks(Environment.TickCount); doubleStartTime = TimeSpan.FromTicks(Environment.TickCount); values = new List<double>(); for (double i = 0; i < maxIteractions; i++) { double res = 0; res = Math.Sin(i); values.Add(res); } doubleEndTime = TimeSpan.FromTicks(Environment.TickCount); arrayStartTime = TimeSpan.FromTicks(Environment.TickCount); rnd = new Random(); source = new Double[maxIteractions]; for (int i = 0; i < maxIteractions; i++) { double dvalue = 0; int ivalue = 0; double v = 0; ivalue = rnd.Next(); dvalue = rnd.NextDouble(); v = dvalue * ivalue; source[i] = v; } destination = new double[maxIteractions]; source.CopyTo(destination, 0); arrayEndTime = TimeSpan.FromTicks(Environment.TickCount); endTime = TimeSpan.FromTicks(Environment.TickCount); testResult = new StringBuilder(); testResult.AppendLine("Task with Lambda"); testResult.AppendLine(); testResult.AppendFormat("Test start: {0}", startTime.ToString()); testResult.AppendLine(); testResult.AppendFormat("Test end: {0}", endTime.ToString()); testResult.AppendLine(); testResult.AppendFormat("Duration delta: {0}", (endTime - startTime).ToString()); testResult.AppendLine(); testResult.AppendFormat("Sin test start ({0} sin(x) calculations): {1}", maxIteractions, doubleStartTime.ToString()); testResult.AppendLine(); testResult.AppendFormat("Sin test end ({0} sin(x) calculations): {1}", maxIteractions, doubleEndTime.ToString()); testResult.AppendLine(); testResult.AppendFormat("Sin test delta ({0} sin(x) calculations): {1}", maxIteractions, (doubleEndTime - doubleStartTime).ToString()); testResult.AppendLine(); testResult.AppendFormat("Random typeof(double) array initialization and copy ({0} elements) start: {1}", maxIteractions, arrayStartTime.ToString()); testResult.AppendLine(); testResult.AppendFormat("Random typeof(double) array initialization and copy (50.000 elements) end: {1}", maxIteractions, arrayEndTime.ToString()); testResult.AppendLine(); testResult.AppendFormat("Random typeof(double) array initialization and copy (50.000 elements) delta: {1}", maxIteractions, (arrayEndTime - arrayStartTime).ToString()); testResult.AppendLine(); Console.WriteLine(testResult.ToString()); Console.WriteLine(); }); task.Start(); if (!async) task.Wait(); } }
La classe è abbastanza semplice. Il metodo Run lancia in maniera sincrona il task che esegue un paio di computazioni trigonometriche e uno spostamento di vettore lineare.
IL TaskWithLambda::Run : vod<bool>
.method public hidebysig instance void Run(bool async) cil managed { // Code size 60 (0x3c) .maxstack 2 .locals init ([0] class [mscorlib]System.Threading.Tasks.Task task, [1] bool CS$4$0000) IL_0000: nop IL_0001: ldnull IL_0002: stloc.0 IL_0003: ldsfld class [mscorlib]System.Action LambdaILTest.TaskWithLambda::'CS$<>9__CachedAnonymousMethodDelegate1' IL_0008: brtrue.s IL_001d IL_000a: ldnull IL_000b: ldftn void LambdaILTest.TaskWithLambda::'b__0'() IL_0011: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_0016: stsfld class [mscorlib]System.Action LambdaILTest.TaskWithLambda::'CS$<>9__CachedAnonymousMethodDelegate1' IL_001b: br.s IL_001d IL_001d: ldsfld class [mscorlib]System.Action LambdaILTest.TaskWithLambda::'CS$<>9__CachedAnonymousMethodDelegate1' IL_0022: newobj instance void [mscorlib]System.Threading.Tasks.Task::.ctor(class [mscorlib]System.Action) IL_0027: stloc.0 IL_0028: ldloc.0 IL_0029: callvirt instance void [mscorlib]System.Threading.Tasks.Task::Start() IL_002e: nop IL_002f: ldarg.1 IL_0030: stloc.1 IL_0031: ldloc.1 IL_0032: brtrue.s IL_003b IL_0034: ldloc.0 IL_0035: callvirt instance void [mscorlib]System.Threading.Tasks.Task::Wait() IL_003a: nop IL_003b: ret } // end of method TaskWithLambda::Run
Il disassemblatore mette subito in evidenza la dimensione del codice. Abbiamo 60 istruzioni nel metodo. E si capisce subito la politica del compilatore rispetto alle lambda. La nostra funzione anonima infatti viene dichiarata all'interno della classe come un qualsiasi altro metodo come è visibile in questa immagine:
e il metodo lambda è il seguente, chiamato dal compilatore <Run>b_0:
IL TaskWithLambda::<Run>b_0 : void()
.method private hidebysig static void '<Run>b__0'() cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Code size 720 (0x2d0) .maxstack 5 .locals init ([0] valuetype [mscorlib]System.TimeSpan startTime, [1] valuetype [mscorlib]System.TimeSpan doubleStartTime, [2] valuetype [mscorlib]System.TimeSpan doubleEndTime, [3] valuetype [mscorlib]System.TimeSpan arrayStartTime, [4] valuetype [mscorlib]System.TimeSpan arrayEndTime, [5] valuetype [mscorlib]System.TimeSpan endTime, [6] class [mscorlib]System.Collections.Generic.List`1<float64> values, [7] float64[] source, [8] float64[] destination, [9] class [mscorlib]System.Random rnd, [10] class [mscorlib]System.Text.StringBuilder testResult, [11] int32 maxIteractions, [12] float64 i, [13] float64 res, [14] int32 V_14, [15] float64 dvalue, [16] int32 ivalue, [17] float64 v, [18] bool CS$4$0000, [19] valuetype [mscorlib]System.TimeSpan CS$0$0001) IL_0000: nop IL_0001: ldnull IL_0002: stloc.s values IL_0004: ldnull IL_0005: stloc.s source IL_0007: ldnull IL_0008: stloc.s destination IL_000a: ldnull IL_000b: stloc.s rnd IL_000d: ldnull IL_000e: stloc.s testResult IL_0010: ldc.i4 0x2625a0 IL_0015: stloc.s maxIteractions IL_0017: call int32 [mscorlib]System.Environment::get_TickCount() IL_001c: conv.i8 IL_001d: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::FromTicks(int64) IL_0022: stloc.0 IL_0023: call int32 [mscorlib]System.Environment::get_TickCount() IL_0028: conv.i8 IL_0029: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::FromTicks(int64) IL_002e: stloc.1 IL_002f: newobj instance void class [mscorlib]System.Collections.Generic.List`1<float64>::.ctor() IL_0034: stloc.s values IL_0036: ldc.r8 0.0 IL_003f: stloc.s i IL_0041: br.s IL_0071 IL_0043: nop IL_0044: ldc.r8 0.0 IL_004d: stloc.s res IL_004f: ldloc.s i IL_0051: call float64 [mscorlib]System.Math::Sin(float64) IL_0056: stloc.s res IL_0058: ldloc.s values IL_005a: ldloc.s res IL_005c: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<float64>::Add(!0) IL_0061: nop IL_0062: nop IL_0063: ldloc.s i IL_0065: ldc.r8 1. IL_006e: add IL_006f: stloc.s i IL_0071: ldloc.s i IL_0073: ldloc.s maxIteractions IL_0075: conv.r8 IL_0076: clt IL_0078: stloc.s CS$4$0000 IL_007a: ldloc.s CS$4$0000 IL_007c: brtrue.s IL_0043 IL_007e: call int32 [mscorlib]System.Environment::get_TickCount() IL_0083: conv.i8 IL_0084: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::FromTicks(int64) IL_0089: stloc.2 IL_008a: call int32 [mscorlib]System.Environment::get_TickCount() IL_008f: conv.i8 IL_0090: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::FromTicks(int64) IL_0095: stloc.3 IL_0096: newobj instance void [mscorlib]System.Random::.ctor() IL_009b: stloc.s rnd IL_009d: ldloc.s maxIteractions IL_009f: newarr [mscorlib]System.Double IL_00a4: stloc.s source IL_00a6: ldc.i4.0 IL_00a7: stloc.s V_14 IL_00a9: br.s IL_00ed IL_00ab: nop IL_00ac: ldc.r8 0.0 IL_00b5: stloc.s dvalue IL_00b7: ldc.i4.0 IL_00b8: stloc.s ivalue IL_00ba: ldc.r8 0.0 IL_00c3: stloc.s v IL_00c5: ldloc.s rnd IL_00c7: callvirt instance int32 [mscorlib]System.Random::Next() IL_00cc: stloc.s ivalue IL_00ce: ldloc.s rnd IL_00d0: callvirt instance float64 [mscorlib]System.Random::NextDouble() IL_00d5: stloc.s dvalue IL_00d7: ldloc.s dvalue IL_00d9: ldloc.s ivalue IL_00db: conv.r8 IL_00dc: mul IL_00dd: stloc.s v IL_00df: ldloc.s source IL_00e1: ldloc.s V_14 IL_00e3: ldloc.s v IL_00e5: stelem.r8 IL_00e6: nop IL_00e7: ldloc.s V_14 IL_00e9: ldc.i4.1 IL_00ea: add IL_00eb: stloc.s V_14 IL_00ed: ldloc.s V_14 IL_00ef: ldloc.s maxIteractions IL_00f1: clt IL_00f3: stloc.s CS$4$0000 IL_00f5: ldloc.s CS$4$0000 IL_00f7: brtrue.s IL_00ab IL_00f9: ldloc.s maxIteractions IL_00fb: newarr [mscorlib]System.Double IL_0100: stloc.s destination IL_0102: ldloc.s source IL_0104: ldloc.s destination IL_0106: ldc.i4.0 IL_0107: callvirt instance void [mscorlib]System.Array::CopyTo(class [mscorlib]System.Array, int32) IL_010c: nop IL_010d: call int32 [mscorlib]System.Environment::get_TickCount() IL_0112: conv.i8 IL_0113: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::FromTicks(int64) IL_0118: stloc.s arrayEndTime IL_011a: call int32 [mscorlib]System.Environment::get_TickCount() IL_011f: conv.i8 IL_0120: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::FromTicks(int64) IL_0125: stloc.s endTime IL_0127: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() IL_012c: stloc.s testResult IL_012e: ldloc.s testResult IL_0130: ldstr "Task with Lambda" IL_0135: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine(string) IL_013a: pop IL_013b: ldloc.s testResult IL_013d: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_0142: pop IL_0143: ldloc.s testResult IL_0145: ldstr "Test start: {0}" IL_014a: ldloca.s startTime IL_014c: constrained. [mscorlib]System.TimeSpan IL_0152: callvirt instance string [mscorlib]System.Object::ToString() IL_0157: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object) IL_015c: pop IL_015d: ldloc.s testResult IL_015f: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_0164: pop IL_0165: ldloc.s testResult IL_0167: ldstr "Test end: {0}" IL_016c: ldloca.s endTime IL_016e: constrained. [mscorlib]System.TimeSpan IL_0174: callvirt instance string [mscorlib]System.Object::ToString() IL_0179: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object) IL_017e: pop IL_017f: ldloc.s testResult IL_0181: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_0186: pop IL_0187: ldloc.s testResult IL_0189: ldstr "Duration delta: {0}" IL_018e: ldloc.s endTime IL_0190: ldloc.0 IL_0191: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::op_Subtraction(valuetype [mscorlib]System.TimeSpan, valuetype [mscorlib]System.TimeSpan) IL_0196: stloc.s CS$0$0001 IL_0198: ldloca.s CS$0$0001 IL_019a: constrained. [mscorlib]System.TimeSpan IL_01a0: callvirt instance string [mscorlib]System.Object::ToString() IL_01a5: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object) IL_01aa: pop IL_01ab: ldloc.s testResult IL_01ad: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_01b2: pop IL_01b3: ldloc.s testResult IL_01b5: ldstr "Sin test start ({0} sin(x) calculations): {1}" IL_01ba: ldloc.s maxIteractions IL_01bc: box [mscorlib]System.Int32 IL_01c1: ldloca.s doubleStartTime IL_01c3: constrained. [mscorlib]System.TimeSpan IL_01c9: callvirt instance string [mscorlib]System.Object::ToString() IL_01ce: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object, object) IL_01d3: pop IL_01d4: ldloc.s testResult IL_01d6: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_01db: pop IL_01dc: ldloc.s testResult IL_01de: ldstr "Sin test end ({0} sin(x) calculations): {1}" IL_01e3: ldloc.s maxIteractions IL_01e5: box [mscorlib]System.Int32 IL_01ea: ldloca.s doubleEndTime IL_01ec: constrained. [mscorlib]System.TimeSpan IL_01f2: callvirt instance string [mscorlib]System.Object::ToString() IL_01f7: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object, object) IL_01fc: pop IL_01fd: ldloc.s testResult IL_01ff: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_0204: pop IL_0205: ldloc.s testResult IL_0207: ldstr "Sin test delta ({0} sin(x) calculations): {1}" IL_020c: ldloc.s maxIteractions IL_020e: box [mscorlib]System.Int32 IL_0213: ldloc.2 IL_0214: ldloc.1 IL_0215: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::op_Subtraction(valuetype [mscorlib]System.TimeSpan, valuetype [mscorlib]System.TimeSpan) IL_021a: stloc.s CS$0$0001 IL_021c: ldloca.s CS$0$0001 IL_021e: constrained. [mscorlib]System.TimeSpan IL_0224: callvirt instance string [mscorlib]System.Object::ToString() IL_0229: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object, object) IL_022e: pop IL_022f: ldloc.s testResult IL_0231: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_0236: pop IL_0237: ldloc.s testResult IL_0239: ldstr "Random typeof(double) array initialization and cop" + "y ({0} elements) start: {1}" IL_023e: ldloc.s maxIteractions IL_0240: box [mscorlib]System.Int32 IL_0245: ldloca.s arrayStartTime IL_0247: constrained. [mscorlib]System.TimeSpan IL_024d: callvirt instance string [mscorlib]System.Object::ToString() IL_0252: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object, object) IL_0257: pop IL_0258: ldloc.s testResult IL_025a: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_025f: pop IL_0260: ldloc.s testResult IL_0262: ldstr "Random typeof(double) array initialization and cop" + "y (50.000 elements) end: {1}" IL_0267: ldloc.s maxIteractions IL_0269: box [mscorlib]System.Int32 IL_026e: ldloca.s arrayEndTime IL_0270: constrained. [mscorlib]System.TimeSpan IL_0276: callvirt instance string [mscorlib]System.Object::ToString() IL_027b: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object, object) IL_0280: pop IL_0281: ldloc.s testResult IL_0283: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_0288: pop IL_0289: ldloc.s testResult IL_028b: ldstr "Random typeof(double) array initialization and cop" + "y (50.000 elements) delta: {1}" IL_0290: ldloc.s maxIteractions IL_0292: box [mscorlib]System.Int32 IL_0297: ldloc.s arrayEndTime IL_0299: ldloc.3 IL_029a: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::op_Subtraction(valuetype [mscorlib]System.TimeSpan, valuetype [mscorlib]System.TimeSpan) IL_029f: stloc.s CS$0$0001 IL_02a1: ldloca.s CS$0$0001 IL_02a3: constrained. [mscorlib]System.TimeSpan IL_02a9: callvirt instance string [mscorlib]System.Object::ToString() IL_02ae: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object, object) IL_02b3: pop IL_02b4: ldloc.s testResult IL_02b6: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendLine() IL_02bb: pop IL_02bc: ldloc.s testResult IL_02be: callvirt instance string [mscorlib]System.Object::ToString() IL_02c3: call void [mscorlib]System.Console::WriteLine(string) IL_02c8: nop IL_02c9: call void [mscorlib]System.Console::WriteLine() IL_02ce: nop IL_02cf: ret } // end of method TaskWithLambda::'<run>b__0'
che è del tutto equivalente al metodo che abbiamo scritto nella classe senza lambda. Genera infatti 720 istruzioni IL.
La differenza tra la classe che utilizza la funzione anonima e quella che non la utilizza è che all'interno del metodo Run è possibile osservare una assegnazione a un delegate di tipo Action il valore dell'indirizzo del metodo nascosto b_0, per poi passare il delegate come parametro alla classe Task.
IL TaskWithoutLambda:: Run : void(bool)
.method public hidebysig instance void Run(bool async) cil managed { // Code size 41 (0x29) .maxstack 2 .locals init ([0] class [mscorlib]System.Threading.Tasks.Task task, [1] bool CS$4$0000) IL_0000: nop IL_0001: ldnull IL_0002: stloc.0 IL_0003: ldarg.0 IL_0004: ldftn instance void LambdaILTest.TaskWithoutLambda::TestTaskOperation() IL_000a: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_000f: newobj instance void [mscorlib]System.Threading.Tasks.Task::.ctor(class [mscorlib]System.Action) IL_0014: stloc.0 IL_0015: ldloc.0 IL_0016: callvirt instance void [mscorlib]System.Threading.Tasks.Task::Start() IL_001b: nop IL_001c: ldarg.1 IL_001d: stloc.1 IL_001e: ldloc.1 IL_001f: brtrue.s IL_0028 IL_0021: ldloc.0 IL_0022: callvirt instance void [mscorlib]System.Threading.Tasks.Task::Wait() IL_0027: nop IL_0028: ret } // end of method TaskWithoutLambda::Run
Il metodo è praticamente identico a meno dell'assegnazione del delegate. Abbiamo 41 istruzioni IL contro le 60 del metodo con lambda.
Differenze tra TaskWithLambda e TaskWithtoutLambda
Riepilogando le differenze, la classe che fa uso della lambda genera un campo statico privato in più che rappresenta il delegate del metodo lambda, utilizzato all'accesso della funzione anonima da parte di altre classi o metodi.Il metodo Run genera 60 istruzioni nel metodo con lambda, contro le 41 del metodo senza lambda.
Il metodo lambda ha la stessa dimensione (720 istruzioni) sia nella classe dove è dichiarato come funzione anonima sia dove è dichiarato come metodo di classe.
Test di esecuzione del codice
Ho effettuato per quanto possibile un test di velocità di esecuzione. Vengono utilizzate sia la classe Task che la classe Thread. Probabilmente il test non è preciso al millesimo in quanto mi sono basato sui Tick esposti dall'OS e quindi dipendente dal clock interno di Windows, ma mi è sembrato il metodo più conveniente per misurare la velocità in questa casistica.Ho effettuato 3 lanci dell'applicativo. I risultati sono i seguenti.
-- Test 1 CPU utilizzate nel test: Intel(R) Core(TM) i7-2675QM CPU @ 2.20GHz Architettura: x64 Famiglia: Intel64 Family 6 Model 42 Stepping 7 Current clock: 2200 Max clock:2200 Numero di cores:4 ---- Task with Lambda Test start: 00:02:24.3435109 Test end: 00:02:24.3435343 Duration delta: 00:00:00.0000234 Sin test start (2500000 sin(x) calculations): 00:02:24.3435109 Sin test end (2500000 sin(x) calculations): 00:02:24.3435250 Sin test delta (2500000 sin(x) calculations): 00:00:00.0000141 Random typeof(double) array initialization and copy (2500000 elements) start: 00:02:24.3435250 Random typeof(double) array initialization and copy (50.000 elements) end: 00:02:24.3435343 Random typeof(double) array initialization and copy (50.000 elements) delta: 00:00:00.0000093 Task without Lambda Test start: 00:02:24.3435343 Test end: 00:02:24.3435593 Duration delta: 00:00:00.0000250 Sin test start (2500000 sin(x) calculations): 00:02:24.3435343 Sin test end (2500000 sin(x) calculations): 00:02:24.3435515 Sin test delta (2500000 sin(x) calculations): 00:00:00.0000172 Random typeof(double) array initialization and copy (2500000 elements) start: 00:02:24.3435515 Random typeof(double) array initialization and copy (2500000 elements) end: 00:02:24.3435593 Random typeof(double) array initialization and copy (2500000 elements) delta: 00:00:00.0000078 Thread with Lambda Test start: 00:02:24.3435593 Test end: 00:02:24.3435718 Duration delta: 00:00:00.0000125 Sin test start (1500000 sin(x) calculations): 00:02:24.3435593 Sin test end (1500000 sin(x) calculations): 00:02:24.3435671 Sin test delta (1500000 sin(x) calculations): 00:00:00.0000078 Random typeof(double) array initialization and copy (1500000 elements) start: 00:02:24.3435671 Random typeof(double) array initialization and copy (1500000 elements) end: 00:02:24.3435718 Random typeof(double) array initialization and copy (1500000 elements) delta: 00:00:00.0000047 Thread without Lambda Test start: 00:02:24.3435734 Test end: 00:02:24.3435859 Duration delta: 00:00:00.0000125 Sin test start (1500000 sin(x) calculations): 00:02:24.3435734 Sin test end (1500000 sin(x) calculations): 00:02:24.3435812 Sin test delta (1500000 sin(x) calculations): 00:00:00.0000078 Random typeof(double) array initialization and copy (1500000 elements) start: 00:02:24.3435812 Random typeof(double) array initialization and copy (1500000 elements) end: 00:02:24.3435859 Random typeof(double) array initialization and copy (1500000 elements) delta: 00:00:00.0000047 -- Test 2 CPU utilizzate nel test: Intel(R) Core(TM) i7-2675QM CPU @ 2.20GHz Architettura: x64 Famiglia: Intel64 Family 6 Model 42 Stepping 7 Current clock: 2200 Max clock:2200 Numero di cores:4 ---- Task with Lambda Test start: 00:02:24.3443000 Test end: 00:02:24.3443234 Duration delta: 00:00:00.0000234 Sin test start (2500000 sin(x) calculations): 00:02:24.3443000 Sin test end (2500000 sin(x) calculations): 00:02:24.3443140 Sin test delta (2500000 sin(x) calculations): 00:00:00.0000140 Random typeof(double) array initialization and copy (2500000 elements) start: 00:02:24.3443140 Random typeof(double) array initialization and copy (50.000 elements) end: 00:02:24.3443234 Random typeof(double) array initialization and copy (50.000 elements) delta: 00:00:00.0000094 Task without Lambda Test start: 00:02:24.3443234 Test end: 00:02:24.3443484 Duration delta: 00:00:00.0000250 Sin test start (2500000 sin(x) calculations): 00:02:24.3443234 Sin test end (2500000 sin(x) calculations): 00:02:24.3443406 Sin test delta (2500000 sin(x) calculations): 00:00:00.0000172 Random typeof(double) array initialization and copy (2500000 elements) start: 00:02:24.3443406 Random typeof(double) array initialization and copy (2500000 elements) end: 00:02:24.3443484 Random typeof(double) array initialization and copy (2500000 elements) delta: 00:00:00.0000078 Thread with Lambda Test start: 00:02:24.3443484 Test end: 00:02:24.3443625 Duration delta: 00:00:00.0000141 Sin test start (1500000 sin(x) calculations): 00:02:24.3443484 Sin test end (1500000 sin(x) calculations): 00:02:24.3443578 Sin test delta (1500000 sin(x) calculations): 00:00:00.0000094 Random typeof(double) array initialization and copy (1500000 elements) start: 00:02:24.3443578 Random typeof(double) array initialization and copy (1500000 elements) end: 00:02:24.3443625 Random typeof(double) array initialization and copy (1500000 elements) delta: 00:00:00.0000047 Thread without Lambda Test start: 00:02:24.3443625 Test end: 00:02:24.3443765 Duration delta: 00:00:00.0000140 Sin test start (1500000 sin(x) calculations): 00:02:24.3443625 Sin test end (1500000 sin(x) calculations): 00:02:24.3443703 Sin test delta (1500000 sin(x) calculations): 00:00:00.0000078 Random typeof(double) array initialization and copy (1500000 elements) start: 00:02:24.3443703 Random typeof(double) array initialization and copy (1500000 elements) end: 00:02:24.3443765 Random typeof(double) array initialization and copy (1500000 elements) delta: 00:00:00.0000062 -- Test 3 CPU utilizzate nel test: Intel(R) Core(TM) i7-2675QM CPU @ 2.20GHz Architettura: x64 Famiglia: Intel64 Family 6 Model 42 Stepping 7 Current clock: 2200 Max clock:2200 Numero di cores:4 ---- Task with Lambda Test start: 00:02:24.3446203 Test end: 00:02:24.3446437 Duration delta: 00:00:00.0000234 Sin test start (2500000 sin(x) calculations): 00:02:24.3446203 Sin test end (2500000 sin(x) calculations): 00:02:24.3446343 Sin test delta (2500000 sin(x) calculations): 00:00:00.0000140 Random typeof(double) array initialization and copy (2500000 elements) start: 00:02:24.3446343 Random typeof(double) array initialization and copy (50.000 elements) end: 00:02:24.3446437 Random typeof(double) array initialization and copy (50.000 elements) delta: 00:00:00.0000094 Task without Lambda Test start: 00:02:24.3446437 Test end: 00:02:24.3446703 Duration delta: 00:00:00.0000266 Sin test start (2500000 sin(x) calculations): 00:02:24.3446437 Sin test end (2500000 sin(x) calculations): 00:02:24.3446609 Sin test delta (2500000 sin(x) calculations): 00:00:00.0000172 Random typeof(double) array initialization and copy (2500000 elements) start: 00:02:24.3446609 Random typeof(double) array initialization and copy (2500000 elements) end: 00:02:24.3446703 Random typeof(double) array initialization and copy (2500000 elements) delta: 00:00:00.0000094 Thread with Lambda Test start: 00:02:24.3446703 Test end: 00:02:24.3446843 Duration delta: 00:00:00.0000140 Sin test start (1500000 sin(x) calculations): 00:02:24.3446703 Sin test end (1500000 sin(x) calculations): 00:02:24.3446796 Sin test delta (1500000 sin(x) calculations): 00:00:00.0000093 Random typeof(double) array initialization and copy (1500000 elements) start: 00:02:24.3446796 Random typeof(double) array initialization and copy (1500000 elements) end: 00:02:24.3446843 Random typeof(double) array initialization and copy (1500000 elements) delta: 00:00:00.0000047 Thread without Lambda Test start: 00:02:24.3446843 Test end: 00:02:24.3446984 Duration delta: 00:00:00.0000141 Sin test start (1500000 sin(x) calculations): 00:02:24.3446843 Sin test end (1500000 sin(x) calculations): 00:02:24.3446921 Sin test delta (1500000 sin(x) calculations): 00:00:00.0000078 Random typeof(double) array initialization and copy (1500000 elements) start: 00:02:24.3446921 Random typeof(double) array initialization and copy (1500000 elements) end: 00:02:24.3446984 Random typeof(double) array initialization and copy (1500000 elements) delta: 00:00:00.0000063
I test presentano dei risultati con variazioni poco apprezzabili tra una configurazione e l'altra.
Conclusioni
Con questi test abbiamo messo in evidenza due fattori importanti:
- A pari condizioni, il codice che fa uso di lambda genera un IL lievemente più grande.
- A pari condizioni, le prestazioni del codice che fa uso di lambda e quelle di codice che non ne fa uso sono pressoché identiche, a meno di ottimizzazioni e ritardi dello scheduler di esecuzioni dell'OS. Le differenze a favore delle lambda, infinitesimali nei nostri esempi, sono propenso a pensare siano dovuti maggiormente allo stato del gestore dei thread rispetto alle possibilità di ottimizzazione del codice. Tesi avvalorata dal fatto che a meno del delegate (che potrebbe permettere un precache del metodo?) e quindi del suo codice di allocazione e assegnazione il codice IL è pressoché identico.
Nessun commento:
Posta un commento