2016-05-06 13 views
3

Ich möchte eine Pipeline erstellen, die Bilder aufnimmt und einige abgeleitete Objekte zurückgibt.SelectMany nimmt sehr viel Speicher mit ReactiveExtensions

Ich benutze eine Sequenz von Bitmaps und für jeden von ihnen ich die Aufgabe (das ist asynchron). Es ist also so einfach wie es scheint. Ich habe jedoch herausgefunden, dass der Speicherverbrauch wirklich hoch ist. Um das Problem zu veranschaulichen, habe ich diesen Test erstellt, den Sie ausführen können.

Bitte schauen Sie sich den Speicher an, da er bis zu 400 MB RAM benötigt.

Was kann ich tun, um so viel Speicher zu vermeiden? Was passiert hier?

[Fact] 
public async Task BitmapPipelineTest() 
{ 
    var bitmaps = Enumerable.Range(0, 100).Select(_ => new WriteableBitmap(800, 600, 96, 96, PixelFormats.Bgr24, new BitmapPalette(new List<Color>() { new Color() }))); 
    var bitmapsObs = bitmaps.ToObservable(); 

    var processed = bitmapsObs.SelectMany(bitmap => DoSomethingAsync(bitmap)); 
    processed.Subscribe(); 

    await Task.Delay(20000); 
} 

private async Task<object> DoSomethingAsync(BitmapSource bitmap) 
{ 
    await Task.Delay(1000); 
    return new object(); 
} 
+1

Wenn Sie ‚Writeablebitmap‘ suchen um und ‚Speicherleck‘, gibt es eine Menge von Plakaten das gleiche Problem auftritt, und ein Mangel an guten Lösungen. Wenn Sie die WriteableBitmap nicht verwenden müssen (dh wenn Sie nicht mit der Benutzeroberfläche arbeiten), würde ich die Bitmap-Klasse verwenden und Ihren Code ändern, um die Bitmap mit einer using-Anweisung in SelectMany zu erstellen/disponieren. – Andrew

+0

@ Andrew - Es könnte mit dem Problem zusammenhängen, das du beschreibst. Ich denke, es hängt von der Version von .NET ab, die das OP verwendet. Es scheint, dass das Speicherproblem in 4.0 angesprochen wurde. In meinen Tests mit .NET 4.5 wächst der Speicher schnell außer Kontrolle, aber es wird schließlich Müll gesammelt, so dass ich glaube nicht, dass es ein Speicherleck ist. –

+0

@JasonBoyd - Interessant, mit 4.5, konnte ich die maximale Speicherzuweisung (2 GB) blasen und den Prozess abstürzen. Ein Aufruf von GC.Collect() nachdem die Bitmap nicht in meinem modifizierten Code enthalten war, hat es auch nicht verhindert (obwohl ich niemals in Erwägung ziehen würde, solch einen GC-Call in Produktion zu setzen) – Andrew

Antwort

2

So glaube ich nicht, das Problem zu SelectMany oder auch reaktiven Erweiterungen notwendigerweise zurückzuführen ist. Es sieht so aus, als ob WriteableBitmap nicht verwalteten Speicher verwendet: source code. Ich glaube, das Problem ist, dass Sie in sehr schneller Folge eine Menge relativ kleiner verwalteter Objekte erstellen, die viel mehr nicht verwalteten Speicher belegen. Vom MSDN:

Wenn ein kleines verwaltete Objekt eine große Menge an nicht verwalteten Speicher zuweist, nimmt die Laufzeit berücksichtigt nur den verwalteten Speicher und unterschätzt damit die Dringlichkeit der Planung der Garbage Collection.

Aber wir können die Garbage Collector Hinweise geben, indem sie die GC.AddMemoryPressure und GC.RemoveMemoryPressure Funktionen. Dies wird dem GC helfen, seine Planung zu verbessern. Bevor wir das tun können, müssen wir eine Vorstellung davon haben, wie viel nicht gemanagter Speicher zugewiesen wird. Ich glaube, dass der nicht verwaltete Speicher verwendet wird, um das Pixelarray zu speichern, so dass ich denke, eine gute Schätzung ist die Pixelbreite mal die Pixelhöhe mal die Anzahl der Bits in jedem Kanal multipliziert mit der Anzahl der Kanäle. Von der MSDN sieht es aus, als ob es 32 Bits (4 Bytes) pro Kanal und 4 Kanäle gibt.

lief ich einige Tests Code ähnlich dem folgenden verwenden und haben wirklich gute Ergebnisse:

var processed = 
    Enumerable 
    .Range(0, 100) 
    .Select(_ => new WriteableBitmap(
     800, 
     600, 
     96, 
     96, 
     PixelFormats.Bgr24, 
     new BitmapPalette(new List<Color>() { new Color() }))) 
    .Select(x => new { Bitmap = x, ByteSize = x.PixelWidth * x.PixelHeight * 4 * 4) 
    .ToObservable() 
    .Do(x => GC.AddMemoryPressure(x.ByteSize)) 
    .SelectMany(x => DoSomethingAsync(x.Bitmap)); 

processed 
.Subscribe(x => GC.RemoveMemoryPressure(x.ByteSize)); 

Allerdings, wenn Ihre Quelle schneller veröffentlicht Bitmaps, als Sie sie Sie werden dann behandeln können immer noch Probleme haben,. Der Gegendruck bewirkt, dass der Speicher schneller zugewiesen wird als freigegeben werden kann.

Ehrlich gesagt, haben Sie wirklich Bitmaps zu Ihnen geschoben? Ich habe keine Ahnung, wie Ihr aktuelles Programm aussieht, aber in Ihrem Beispielcode ist das ein Pull-basiertes System. Wenn es sich um ein Pull-basiertes System handelt, haben Sie in Betracht gezogen PLINQ? PLINQ ist großartig für diese Art von Ding; es gibt Ihnen wirklich gute Kontrolle über Nebenläufigkeit und Sie müssen sich nicht über Gegendruck sorgen.

+0

Große (und korrekte) Verwendung einer 'Do'-Anweisung. Gut, ein Beispiel zu sehen, das nicht nur protokolliert. –

+0

@LeeCampbell - Requisiten von dem Mann, der das Buch geschrieben hat. Genial! –

1

Es scheint mir, dass Sie in eine einfache Speicherbelegung Problem auftreten.

Wenn es 4 Bytes pro Kanal und 4 Kanäle pro Pixel gibt, dann sind Ihre 1000 Bilder bei 800 x 600 jeweils 1000 x 800 x 600 x 4 x 4 = 733MB (ungefähr).

Was mir jedoch in Ihrem Code auffällt, der Ihnen Kummer bereiten könnte, ist, dass Sie mit einem Aufzählungszeichen beginnen und es dann in ein Observable verwandeln, das mithilfe von Tasks erstellt wird, die Sie am Ende ausführen asynchron mit einem Feuer und vergessen .Subscribe() und Sie täuschen die Rückkehr mit einem await Task.Delay(20000);. Es ist alles anfällig für Fehler. Sie sollten es vermeiden, Ihre "Monaden" zu mischen.

Hier ist, wie ich es schreiben würde:

public async Task BitmapPipelineTest() 
{ 
    await 
     Observable 
      .Range(0, 100) 
      .Select(_ => new WriteableBitmap(
       800, 600, 96, 96, 
       PixelFormats.Bgr24, 
       new BitmapPalette(new List<Color>() { new Color() }))) 
      .SelectMany(x => 
       Observable 
        .Start(() => 
        { 
         Thread.Sleep(10); 
         return new object(); 
        })); 
} 
Verwandte Themen