2014-12-17 4 views
9

Ich fand ein unangenehmes Verhalten mit Visual Studio. Es hängte meine Maschine auf, während ich C# kompilierte.C# Compiler (Csc.exe) Speicherüberlauf Compiling geschachtelte Typen und Linq

Ich habe Verhalten auf die nächste minimale Quellcode reduziert

using System.Collections.Generic; 
using System.Linq; 

namespace memoryOverflowCsharpCompiler { 

    class SomeType { public decimal x; } 

    class TypeWrapper : Dictionary<int, 
         Dictionary<int, 
         Dictionary<int, SomeType [] []>>> { 

     public decimal minimumX() { 
      return base.Values.Min(a => 
         a.Values.Min(b => 
         b.Values.Min(c => 
         c  .Sum(d => 
         d  .Sum(e => e.x))))); 
     } 
    } 
} 

Kompilieren mit

PROMPT> csc source.cs 

    *** BANG! overflow memory usage (up to ~3G) 

PROMPT> csc /? 
Microsoft (R) Visual C# Compiler version 12.0.30501.0 
Copyright (C) Microsoft Corporation. All rights reserved. 
... 

(mit Windows 8.1 Pro N x64; csc Compiler-Prozess mit 32 Bit ausgeführt wird)

Anschauliche Änderungen führen nicht zu diesem Verhalten (zB Änderung decimal durch int, Reduzierung eines verschachtelten Levels l, ...), ein großes Select dann Durchführung Reduzierung funktioniert

Explicit Abhilfe:

  return base.Values.SelectMany(a => 
         a.Values.SelectMany(b => 
         b.Values.Select (c => 
         c.  Sum  (d => 
         d.  Sum  (e => e.x))))).Min(); 

Obwohl diese explizite Abhilfe besteht, ist es nicht garantiert, dass dieses Verhalten nicht wieder auftreten wird.

Was ist los?

Vielen Dank!

+1

Aus Interesse, haben Sie versucht, dies mit der VS2015 Vorschau ? Es wäre interessant zu wissen, ob es in Roslyn behoben ist. –

+0

Dieser Code hat mehrere Dinge, die es schwierig machen, die Typen zu folgern: 1) Implizite Konvertierungen 2) Viele Überladungen 3) verschachtelte Lambdas. Wenn Sie sich stark genug bemühen, können Sie NP-vollständige SAT-Probleme in diese codieren, so dass der Compiler nicht alle Überladungsauflösungsprobleme effizient lösen kann. Aber ich weiß nicht, warum dein Beispiel so schlecht ist. – CodesInChaos

+0

@JonSkeet Alle Freiwilligen? (Ich kann nicht) Ich habe darüber gesucht, aber nicht gefunden :( – josejuan

Antwort

3

Es scheint, dass die generische Typauflösung in diesem Fall fehlschlägt. Der Wechsel von decimal zu int funktioniert zufällig. Wenn Sie die Verschachtelungsebene erhöhen, sehen Sie, dass es auch für int fehlschlägt. Auf meinem x64-Computer kompiliert dieser Code für int und decimal und verwendet etwa 2,5 GB Arbeitsspeicher, aber zunehmende Verschachtelungsebene führt zu einem Überlauf, wenn die Speicherauslastung auf rund 4 GB wächst.

Angabe Typargument erlaubt explizit Code zu kompilieren:

class TypeWrapper : Dictionary<int, Dictionary<int, Dictionary<int, Dictionary<int, SomeType[][]>>>> 
{ 
    public decimal minimumX() 
    { 
     return base.Values 
      .Min<Dictionary<int, Dictionary<int, Dictionary<int, SomeType[][]>>>, decimal>(a => a.Values 
       .Min<Dictionary<int, Dictionary<int, SomeType[][]>>, decimal>(b => b.Values 
        .Min<Dictionary<int, SomeType[][]>, decimal>(c => c.Values 
         .Min(d => d 
          .Sum(e => e.Sum(f => f.x)) 
         ) 
        ) 
       ) 
      ); 
    } 
} 

Auch Compiler funktioniert, wenn Sie durch die Einführung von lokalen Variablen reduce nisten:

class TypeWrapper : Dictionary<int, Dictionary<int, Dictionary<int, Dictionary<int, SomeType[][]>>>> 
{ 
    public decimal minimumX() 
    { 
     Func<Dictionary<int, SomeType[][]>, decimal> inner = (Dictionary<int, SomeType[][]> c) => c.Values 
         .Min(d => d 
          .Sum(e => e.Sum(f => f.x)) 
         ); 

     return base.Values 
      .Min(a => a.Values 
       .Min(b => b.Values 
        .Min(inner) 
       ) 
      ); 
    } 
}