Ich habe eine ziemlich einfache Aufgabe in C# zu erreichen. Der Benutzer gibt mir einen Stream, der eine große Binärdatei darstellt (mehrere zehn GB groß). Die Datei besteht aus vielen und vielen verschiedenen Blöcken. Ich muss jeden Block lesen; Führen Sie für jeden Block eine CPU-intensive Analyse durch. dann geben Sie dem Benutzer die Ergebnisse - in der richtigen Reihenfolge. In Pseudo-Code, könnte dieser Code wie folgt aussehen:Parallele Pipeline mit geordneten Ein- und Ausgängen
public IEnumerable<TResult> ReadFile(Stream inputStream) {
while(true) {
byte[] block = ReadNextBlock(stream);
if (block == null) {
break; // EOF
}
TResult result = PerformCpuIntensiveAnalysis(block);
yield return result;
}
}
Dies richtig funktioniert, aber langsam, da es nur ist ein CPU-Kern für die CPU-intensive Analyse. Ich möchte die Blöcke einzeln lesen, parallel analysieren und dann die Ergebnisse an den Benutzer in derselben Reihenfolge zurückgeben, in der die Blöcke in der Datei gefunden wurden. Natürlich kann ich nicht die gesamte Datei im Speicher lesen, daher möchte ich die Anzahl der Blöcke, die ich in der Warteschlange habe, jederzeit begrenzen.
Es gibt viele Lösungen, und ich habe ein paar versucht; aber aus irgendeinem Grund kann ich keine Lösung finden, die deutlich den naiven Ansatz übertrifft:
public IEnumerable<TResult> ReadFile(Stream inputStream) {
while(true) {
var batch = new List<byte[]>();
for (int i=0; i<BATCH_SIZE; i++) {
byte[] block = ReadNextBlock(stream);
if (block == null) {
break;
}
batch.Add(block);
}
if (batch.Count == 0) {
break;
}
foreach(var result in batch
.AsParallel()
.AsOrdered()
.Select(block => PerformCpuIntensiveAnalysis(block))
.ToList()) {
yield return result;
}
}
}
I TPL/Datenflüsse sowie die rein manuelle Ansatz ausprobiert habe, und in jedem Fall mein Code verbringt die meiste Zeit warten auf die Synchronisation. Es übertrifft die serielle Version um etwa das Doppelte, aber auf einer Maschine mit 8 Kernen würde ich mehr erwarten. Also, was mache ich falsch?
(Ich sollte auch klarstellen, dass ich die „yield returns“ Generator Muster in meinem Code nicht wirklich bin mit, verwende ich es gerade hier der Kürze halber.)
Ohne eine gute [MCVE], die das Problem zuverlässig reproduziert, ist es unmöglich, sicher zu wissen. Sie sollten jedoch die Möglichkeit in Betracht ziehen, dass Ihr Engpass I/O und nicht CPU ist. Wenn I/O Ihr Flaschenhals ist, können Sie Threads und CPU-Kerne hinzufügen, bis die Kühe nach Hause kommen, und es wird immer noch nicht gut sein. –
@Peter Duniho: Welche anderen Informationen benötigen Sie für das Beispiel? In Bezug auf I/O vs. CPU habe ich versucht, eine kleinere Datei (~ 100 MB) in einen Speicherpuffer zu laden und dann als MemoryStream zum Testen einzupacken - also bin ich mir ziemlich sicher, dass I/O nicht der ist Engpass. – Bugmaster
Haben Sie versucht, einen Profiler (z. B. PerfView) zu verwenden, um zu ermitteln, wo der Engpass auftritt? – easuter