2015-01-28 9 views
14

Während ich die .NET-Speicherzuweisung meines Codes mit dem Visual Studio 2013-Leistungsassistenten analysierte, bemerkte ich eine bestimmte Funktion, die viele Bytes zuweist (da sie in einer großen Schleife aufgerufen wird). Aber wenn ich mir die Funktion ansehe, die im Profiling-Bericht hervorgehoben ist, habe ich nicht verstanden, warum überhaupt Speicher zugewiesen wurde.nicht ausführende linq verursacht Speicherzuweisung C#

Um besser zu verstehen, was passiert ist, isolierte ich den Code, der die Zuweisungen verursachte. Dies war ähnlich der LinqAllocationTester Klasse unten.

Sobald ich den LINQ-Code in dieser Funktion auskommentiert habe, die im getesteten Codepfad sowieso nie ausgeführt wurde, wurde kein Speicher mehr zugewiesen. Die NonLinqAllocationTester-Klasse imitiert dieses Verhalten. Das Ersetzen des LINQ-Codes durch eine normale Schleife lässt auch keine Speicherzuweisungen zu.

Wenn ich den .NET-Speicherzuweisungstest für den Testcode unten ausführen, zeigt es, dass der LinqAllocationTester 100.000 Zuordnungen (1 pro Aufruf) verursacht, während der NonLinqAllocationTester keine hat. Beachten Sie, dass useLinq immer falsch ist, so dass der LINQ-Code selbst nie ausgeführt wird.

Function Name     | Inclusive | Exclusive | Inclusive | Exclusive 
           | Allocations | Allocations | Bytes  | Bytes 
------------------------------------------------------------------------------------- 
LinqAllocationTester.Test(int32) |  100.000 |  100.000 | 1.200.000 | 1.200.000 
Program.Main(string[])   |  100.000 |   0 | 1.200.000 |   0 

Warum funktioniert das nicht ausführen LINQ Code Ursache Speicherzuordnungen? Und gibt es eine Möglichkeit dies zu verhindern, neben der Vermeidung von LINQ-Funktionen?

class Program { 
    static void Main(string[] args) { 
     List<int> values = new List<int>() { 1, 2, 3, 4 }; 
     LinqAllocationTester linqTester = new LinqAllocationTester(false, values); 
     NonLinqAllocationTester nonLinqTester = new NonLinqAllocationTester(false, values); 

     for (int i = 0; i < 100000; i++) { 
      linqTester.MaxDifference(i); 
     } 

     for (int i = 0; i < 100000; i++) { 
      nonLinqTester.MaxDifference(i); 
     } 
    } 
} 

internal class LinqAllocationTester { 
    private bool useLinq; 
    private List<int> values; 

    public LinqAllocationTester(bool useLinq, List<int> values) { 
     this.useLinq = useLinq; 
     this.values = values; 
    } 

    public int MaxDifference(int value) { 
     if (useLinq) { 
      return values.Max(x => Math.Abs(value - x)); 
     } else { 
      int maxDifference = int.MinValue; 
      foreach (int value2 in values) { 
       maxDifference = Math.Max(maxDifference, Math.Abs(value - value2)); 
      } 
      return maxDifference; 
     } 
    } 
} 

internal class NonLinqAllocationTester { 
    private bool useLinq; 
    private List<int> values; 

    public NonLinqAllocationTester(bool useLinq, List<int> values) { 
     this.useLinq = useLinq; 
     this.values = values; 
    } 

    public int MaxDifference(int value) { 
     if (useLinq) { 
      return 0; 
     } else { 
      int maxDifference = int.MinValue; 
      foreach (int value2 in values) { 
       maxDifference = Math.Max(maxDifference, Math.Abs(value - value2)); 
      } 
      return maxDifference; 
     } 
    } 
} 

Antwort

7

Sie können einen Blick auf die erzeugte IL nehmen wird die DisplayClass für die LINQ-Ausdruck am Anfang des Verfahrens außerhalb der ersten if-Zweig initialisiert, um zu sehen, dass. Dies liegt daran, dass es den Abschluss für den Lambda-Ausdruck zu Beginn der Methode erzeugt (wobei der Wert zuerst erscheint).

IL:

IL_0000: ldnull 
IL_0001: stloc.2 
IL_0002: newobj instance void ConsoleApplication2.LinqAllocationTester/'<>c__DisplayClass2'::.ctor() 
IL_0007: stloc.3 
IL_0008: ldloc.3 
IL_0009: ldarg.1 
IL_000a: stfld int32 ConsoleApplication2.LinqAllocationTester/'<>c__DisplayClass2'::'value' 
IL_000f: nop 
IL_0010: ldarg.0 
IL_0011: ldfld bool ConsoleApplication2.LinqAllocationTester::useLinq 
IL_0016: ldc.i4.0 
IL_0017: ceq 
IL_0019: stloc.s CS$4$0001 
IL_001b: ldloc.s CS$4$0001 
IL_001d: brtrue.s IL_0042 

Wenn Sie Ihren Wert auf einen engeren Bereichs-Variablen wie folgt kopieren:

if (useLinq) 
{ 
    int value2 = value; 
    return values.Max(x => Math.Abs(value2 - x)); 
} 

die zusätzlichen Zuweisungen nicht mehr passieren sollte.