2014-12-17 11 views
9

Znalazłem jedno nieprzyjemne zachowanie rozwijające się w Visual Studio. To wisiało na mojej maszynie podczas kompilacji C#.Kompresowanie pamięci kompilatora C# (csc.exe) kompilujące typy zagnieżdżone i Linq

mam zmniejszone zachowanie do następnego kodu minimalnym źródłowego

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))))); 
     } 
    } 
} 

kompilacji z

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. 
... 

(używając Windows 8.1 Pro N 64; csc proces kompilator jest uruchomiony z 32bits)

drastyczne modyfikacje nie powodują takiego zachowania (np. zmiana decimal przez int, zmniejszając jeden zagnieżdżony leve l, ...), wykonując wielki Select następnie redukcji, działa dobrze

Explicit Obejście:

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

Mimo to jawne obejście istnieje, to nie gwarantuje, że takie zachowanie nie powtórzy.

Co jest nie tak?

Dziękujemy!

+1

z odsetek, próbowałeś to z podglądem VS2015 ? Byłoby ciekawie wiedzieć, czy jest to ustalone w Roslyn. –

+0

Ten kod ma kilka rzeczy, które utrudniają wnioskowanie o typach: 1) Niejawne konwersje 2) Wiele przeciążeń 3) zagnieżdżonych lambd. Jeśli spróbujesz wystarczająco mocno, możesz zakodować NP pełne problemy SAT w tych, więc kompilator nie może skutecznie rozwiązać wszystkich problemów z przeciążeniem + rozwiązywanie problemów typu. Ale nie wiem, dlaczego twój przykład jest taki zły. – CodesInChaos

+0

@JonSkeet Każdy wolontariusz? (Nie mogę) Szukałem o tym, ale nie znaleziono :( – josejuan

Odpowiedz

3

Wygląda na to, że w takim przypadku typowe rozwiązywanie typu nie powiedzie się. Zmiana z decimal na int działa przez przypadek. Jeśli zwiększysz poziom zagnieżdżenia, wtedy zobaczysz, że nie działa on również dla int. Na moim komputerze z procesorem x64 kod ten kompiluje się zarówno dla int i decimal i wykorzystuje około 2,5 GB pamięci, ale zwiększenie poziomu zagnieżdżenia powoduje przepełnienie, gdy wykorzystanie pamięci rośnie do 4 GB.

Określanie typu argumentacja wyraźnie dopuszcza do kompilacji kodu:

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)) 
         ) 
        ) 
       ) 
      ); 
    } 
} 

także kompilatora prace przy zmniejszyć gniazdowania poprzez wprowadzenie zmiennej lokalnej:

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) 
       ) 
      ); 
    } 
}