Parallel.ForEach kann eine "Out of Memory" -Ausnahme verursachen, wenn mit einem Enumerable mit einem großen Objekt gearbeitet wird
Ich versuche zu migrieren einer Datenbank, wo die Bilder, wo die in der Datenbank gespeichert, um einen Datensatz in der Datenbank zeigt auf eine Datei auf der Festplatte. Ich versuchte zu verwenden Parallel.ForEach
um den Prozess zu beschleunigen mit dieser Methode zur Abfrage der Daten.
Jedoch bemerkte ich, dass ich immer ein OutOfMemory
Ausnahme. Ich weiß Parallel.ForEach
wird eine batch-Abfrage von enumerables zur Milderung der Kosten für overhead -, wenn es eine für den Abstand der Abfragen aus (also deine Quelle mehr wahrscheinlich wird der nächste Datensatz im Arbeitsspeicher zwischengespeichert, wenn Sie eine Reihe von Abfragen auf einmal anstelle von Abstand Sie aus). Das Thema ist aus einem der Datensätze, die ich fahre ist ein 1-4Mb byte-array, das Zwischenspeichern ist, die den gesamten Adressraum verwendet werden (Das Programm muss laufen in x86-Modus als target-Plattform die 32-bit-Maschine)
Gibt es eine Möglichkeit zum deaktivieren der Zwischenspeicherung oder machen kleinere für die TPL?
Hier ist ein Beispielprogramm, um zu zeigen das Problem. Diese müssen beachtet werden im x86-Modus, um zu zeigen, das Problem, wenn es zu lange oder es ist nicht passiert, auf Ihrem Computer bump up der Größe des Arrays (fand ich 1 << 20
dauert etwa 30 Sekunden, auf meinem Rechner und 4 << 20
war fast augenblicklich)
class Program
{
static void Main(string[] args)
{
Parallel.ForEach(CreateData(), (data) =>
{
data[0] = 1;
});
}
static IEnumerable<byte[]> CreateData()
{
while (true)
{
yield return new byte[1 << 20]; //1Mb array
}
}
}
ParallelOptions.MaxDegreeOfParallelism
- Wert helfen? InformationsquelleAutor der Frage Scott Chamberlain | 2011-08-08
Du musst angemeldet sein, um einen Kommentar abzugeben.
Den Standard-Optionen für
Parallel.ForEach
nur gut funktionieren, wenn der task die CPU-gebunden und skaliert Linear. Wenn der task die CPU-gebunden ist, funktioniert alles perfekt. Wenn Sie einen quad-core und keine anderen Prozesse laufen, dannParallel.ForEach
nutzt alle vier Prozessoren. Wenn Sie einen quad-core und einige andere Prozess auf Ihrem computer mit einer CPU, dannParallel.ForEach
verwendet, etwa drei Prozessoren.Aber wenn die Aufgabe ist nicht CPU-gebunden ist, dann
Parallel.ForEach
fängt immer Aufgaben, versuchen schwer zu halten alle CPUs beschäftigt. Doch egal, wie viele Aufgaben laufen parallel, es gibt immer mehr ungenutzte CPU-Leistung und so hält es mit der Erstellung von Aufgaben.Wie können Sie sagen, wenn Ihre Aufgabe ist die CPU-gebunden ist? Hoffentlich nur durch Einsicht. Wenn Sie factoring Primzahlen, es ist offensichtlich. Aber auch andere Fälle sind nicht so offensichtlich. Der empirische Weg, zu sagen, wenn Ihre Aufgabe ist die CPU-gebunden ist, zu begrenzen den maximalen Grad der Parallelität mit
ParallelOptions.MaximumDegreeOfParallelism
und beobachten, wie sich Ihr Programm verhält. Wenn Ihre Aufgabe ist die CPU-gebunden ist, dann sollten Sie ein Muster wie dieses auf einem quad-core-system:ParallelOptions.MaximumDegreeOfParallelism = 1
: verwenden Sie eine volle CPU-oder 25% CPU-AuslastungParallelOptions.MaximumDegreeOfParallelism = 2
: zwei CPUs oder 50% CPU-AuslastungParallelOptions.MaximumDegreeOfParallelism = 4
: alle CPUs oder 100% CPU-AuslastungIst, verhält es sich wie diese dann verwenden Sie die Standard -
Parallel.ForEach
Optionen und gute Ergebnisse erzielen. Lineare CPU-Auslastung bedeutet gute Terminplanung.Aber wenn ich führen Sie die Beispielanwendung auf meinem Intel i7, ich bekomme über 20% CPU-Auslastung, egal was maximalen Grad der Parallelität ich. Warum ist das so? So viel Speicher alloziert werden, dass der garbage collector blockieren von threads. Die Anwendung Ressourcen gebunden, und die Ressource wird Arbeitsspeicher.
Ebenfalls eine I/O-bound Aufgabe führt, dass die lang Laufenden Abfragen gegen eine Datenbank-server wird auch nie in der Lage sein, effektiv zu nutzen, alle CPU-Ressourcen auf dem lokalen computer verfügbar. Und in Fällen wie, dass der Taskplaner nicht in der Lage ist zu "wissen, Wann zu stoppen, starten neuer Aufgaben.
Wenn Ihre Aufgabe ist es nicht CPU-bound oder CPU-Auslastung nicht Linear skaliert mit den maximalen Grad der Parallelität, dann sollten Sie sich beraten
Parallel.ForEach
nicht zu starten, zu viele Aufgaben auf einmal. Der einfachste Weg ist, um eine Rufnummer anzugeben, die es erlaubt gewisse Parallelität für überlappende I/O-bound tasks, aber nicht so viel, dass Sie überwältigen den lokalen computer auf die Nachfrage nach Ressourcen oder überfordern, alle remote-Servern. Versuch und Irrtum beteiligt ist, die besten Ergebnisse zu erhalten:InformationsquelleAutor der Antwort Rick Sladkey
So, während das, was Rick vorgeschlagen hat ist auf jeden Fall ein wichtiger Punkt, eine andere Sache, denke ich, fehlt, ist die Diskussion von partitionieren.
Parallel::ForEach
einen Standard -Partitionierer<T>
Umsetzung, die für eineIEnumerable<T>
die keine bekannten Länge, wird mit einem chunk-Partitionierung-Strategie. Was dies bedeutet ist jeder worker-thread dieParallel::ForEach
ist verwenden, um die Daten festgelegt wird, Lesen Sie einige Anzahl der Elemente aus derIEnumerable<T>
wird nur dann verarbeitet, dass Gewinde und ignoriert die Arbeit stehlen für jetzt). Es tut dies, um zu sparen Sie den Aufwand, ständig zu müssen, gehen Sie zurück zu der Quelle und ordnen Sie sich neue Arbeits-und Zeitplan für ein weiteres worker-thread. So, in der Regel, dies ist eine gute Sache.Jedoch, in Ihrem spezifischen Szenario, Stell dir vor du bist auf einem quad-core und Sie habenMaxDegreeOfParallelism
zu 4 threads für Ihre Arbeit und nun ist jeder von denen zieht ein Stück 100 Elemente aus derIEnumerable<T>
. Gut, dass das 100-400 MB Recht gibt es nur für die jeweilige worker-thread, richtig?So, wie Sie dieses Problem lösen? Einfach, Sie schreiben Sie einen benutzerdefinierten
Partitionierer<T>
Umsetzung. Nun, chunking ist immer noch nützlich in Ihrem Fall, so dass Sie wahrscheinlich nicht wollen, zu gehen mit ein einzelnes element partitioning-Strategie, weil dann würden Sie die Einführung overhead mit allen Aufgabe-Koordination erforderlich. Stattdessen würde ich schreiben, eine konfigurierbare version, die Sie einstellen können, über eine " appsetting, bis Sie die optimale balance für Ihren Arbeitsaufwand. Die gute Nachricht ist, dass, während das schreiben einer solchen Implementierung ist ziemlich straightfoward, Sie haben tatsächlich nicht zu auch es selbst schreiben, weil die PFX-team bereits gemacht und legen Sie es in die parallele Programmierung-Beispiele Projekt.InformationsquelleAutor der Antwort Drew Marsh
Dieses Problem hat, alles zu tun, Partitionierer, nicht mit dem Grad der Parallelität. Die Lösung ist die Implementierung von benutzerdefinierten Daten-Partitionierer.
Wenn das dataset groß, es scheint, die mono-Umsetzung des TPL ist garantiert
run out of memory.Dieses geschah mir vor kurzem (im wesentlichen war ich mit den obigen Schleife, und gefunden, dass der Speicher erhöht sich Linear, bis er mir eine OOM-exception).
Nachdem die Ablaufverfolgung des Problems, fand ich, dass standardmäßig mono wird aufteilen
enumerator mit einem EnumerablePartitioner Klasse. Diese Klasse hat einen
Verhalten, dass jedes mal, es gibt Daten aus, um eine Aufgabe, die er "Brocken"
die Daten, die von einer stetig wachsenden (und unveränderlich) Faktor 2. So die ersten
mal eine Aufgabe fragt nach Daten, die es bekommt, ein Stück Größe 1, das nächste mal von der Größe
2*1=2, das nächste mal 2*2=4, dann 2*4=8 usw. etc. Das Ergebnis ist, dass die
Menge der Daten, die übergeben, um die Aufgabe, und daher im Speicher abgelegt
gleichzeitig steigt mit der Dauer der Aufgabe, und wenn eine Menge von Daten
verarbeitet wird, einen out-of-memory-Ausnahme unweigerlich Auftritt.
Vermutlich der ursprüngliche Grund für dieses Verhalten ist, dass er vermeiden will,
unter jedem thread return mehrmals, um Informationen zu bekommen, aber es scheint zu werden
basierend auf der Annahme, dass alle Daten verarbeitet werden könnten, fit in memory
(nicht der Fall beim Lesen von großen Dateien).
Dieses Problem kann vermieden werden, mit der eine benutzerdefinierte Partitionierer wie vorher angegeben. Ein Allgemeines Beispiel für eine, die einfach die Daten zurück, die für jede Aufgabe ein Element zu einem Zeitpunkt ist hier:
https://gist.github.com/evolvedmicrobe/7997971
Einfach instanziieren Sie die Klasse zunächst in Parallel.Anstelle der enumerable selbst
InformationsquelleAutor der Antwort evolvedmicrobe