2016-08-16 2 views
0

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

+0

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

+0

@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

+0

Haben Sie versucht, einen Profiler (z. B. PerfView) zu verwenden, um zu ermitteln, wo der Engpass auftritt? – easuter

Antwort

1

Versuchen Sie, die Blockgröße zu optimieren.

Wenn es zu wenige Blöcke gibt und einer von ihnen viel länger als die anderen dauert, dann muss nur eine CPU fast die gesamte Arbeit erledigen.

Auf der anderen Seite, wenn die Blöcke zu klein sind, wird TPL viel Zeit mit Overhead in Bezug auf Task-Management verbringen.

Sie sollten wesentlich mehr Blöcke als CPUs haben. Dadurch kann TPL die Arbeit gleichmäßig auf die CPUs verteilen. Auf der anderen Seite sollte ein Block eine erhebliche Rechenarbeit erfordern. Es ist schwer, konkrete Zahlen zu geben, also.

+0

Können Sie eine gute Möglichkeit zur dynamischen Skalierung der Puffergröße empfehlen? Ich habe keine Kontrolle über die Größe der Blöcke; oder besser, ich weiß, dass jeder Block etwa 64K groß sein wird, aber ich weiß nicht, wie lange es dauern wird, um zu verarbeiten. Aber, ich könnte die Blöcke dynamisch aufteilen ... – Bugmaster

+0

Ich würde nur Experimente machen. Versuchen Sie eine Blockgröße, die 4 mal kleiner oder größer ist, und sehen Sie, ob Sie eine Verbesserung sehen oder ob sie sich verschlechtert. –

Verwandte Themen