2013-02-19 8 views
19

Siehe folgende gleichzeitige Leistungsanalyse, welche die durch eine parallele foreach geleisteten Arbeit:Warum habe ich hier ein Schloss?

enter image description here

Innerhalb der Schleife jeder Thread Daten aus der DB liest und sie verarbeiten. Zwischen den Threads gibt es keine Sperren, da sie jeweils unterschiedliche Daten verarbeiten.

Sieht aus, als gäbe es in allen Threads der foreach aus unbekannten Gründen periodische Sperren (siehe die schwarzen vertikalen Rechtecke). Wenn Sie das ausgewählte gesperrte Segment (das dunkelrote) sehen, sehen Sie, dass der Stapel den im StockModel.Quotation-Konstruktor gesperrten Thread anzeigt. Der Code dort erstellt nur zwei leere Listen!

Ich habe irgendwo gelesen, dass dies durch die GC verursacht werden könnte, so habe ich die Garbage Collection geändert mit im Server-Modus ausgeführt wird:

<runtime> 
    <gcServer enabled="true"/> 
</runtime> 

Ich habe eine kleine Verbesserung (ca. 10% - 15 % schneller), aber ich habe immer noch die vertikalen Sperren.

Ich habe auch alle DB-Abfragen der WITH (NOLOCK) hinzugefügt, da ich nur Daten ohne Unterschied lese.

Irgendwelche Hinweise darauf, was hier passiert?

Der Computer, auf dem die Analyse durchgeführt wurde, hat 8 Kerne.

EDIT: Nach der Aktivierung von Microsoft Symbol-Servern stellt sich heraus, dass alle Threads bei Aufrufen wie wait_gor_gc_done oder WaitUntilGCComplete blockiert sind. Ich dachte, dass GCServer aktivieren ich einen GC für jeden Thread hatte, so würde ich die "vertikale" Sperre vermeiden, aber scheint, dass es nicht der Fall ist. Liege ich falsch? Die zweite Frage: Da die Maschine nicht unter Speicherdruck steht (5 von 8 Gigs werden verwendet) gibt es eine Möglichkeit, die GC-Ausführung zu verzögern oder zu unterbrechen, bis die parallele foreach endet (oder weniger häufig zu feuern))

+0

Wenn Sie eine Menge Objekte zuweisen und die Sperren tatsächlich vom GC verursacht werden, haben Sie versucht, einen GC.Collect zu erzwingen, bevor Sie mit der TPL arbeiten? GC.Collect mit GCCollectionMode.Forced. – Alex

+0

Nun, ich ordne innerhalb der Schleife eine große Menge kleiner Objekte zu, die am Ende jeder Iteration "aufgegeben" werden. Könnte dies die ganze Menge von Threads sperren, wenn sie GC'ed sind? –

+4

Aktivieren Sie den Microsoft Symbol Server, um bessere Stack-Traces zu erhalten. Angesichts der langen Wartezeit sieht das einfach nach einfachen Speicherbereinigungen aus. –

Antwort

0

Sie könnten versuchen, GCLatencyMode.LowLatency; Siehe ähnliche Frage mit hier: Prevent .NET Garbage collection for short period of time

ich dies ohne Glück vor kurzem versucht. Beim Zwischenspeichern von Bitmap-Bildern mit Symbolgrößen in einem Formular, das ich gerade anzeigte, wurde noch die Garbage-Collection aufgerufen. Was für mich funktionierte, war die Verwendung von Ants Performance Profiler und Reflector, um die genauen Anrufe zu finden, die das GC.Collect verursacht und umgangen haben.

+0

Nichts Seltsames hier. Ich ordne große Mengen von Objekten für eine kurze Zeitperiode zu, aber die Maschine hat viel RAM, so würde ich es vorziehen, für eine Weile den GC zu "stoppen", wenn ich es tun könnte. –

+0

Haben Sie Zuweisungen innerhalb einer Schleife? Können sie nach draußen bewegt werden? siehe http://stackoverflow.com/questions/3412003/allocating-memory-inside-loop-vs-outside-loop – Kim

+0

Mmmm, liest jede Schleife ihre eigenen Daten aus einer db, verarbeitet sie und erzeugt ein Ergebnis, dass sie für weitere gespeichert wird wird bearbeitet. Danach werden die generierten Daten verworfen. Ich sehe keinen Weg, das zu vermeiden ... –

3

Wenn Ihre StockModel.Quotation-Klasse dies zulässt, könnten Sie einen Pool erstellen, um die Anzahl der neu erstellten Objekte zu begrenzen. Dies ist eine Technik, die sie manchmal in Spielen verwenden, um zu verhindern, dass der Garbage Collector in der Mitte rendert.

Hier ist eine grundlegende Pool Umsetzung:

class StockQuotationPool 
    { 

     private List<StockQuotation> poolItems; 
     private volatile int itemsInPool; 

     public StockQuotationPool(int poolSize) 
     { 
      this.poolItems = new List<StockQuotation>(poolSize); 
      this.itemsInPool = poolSize; 

     } 

     public StockQuotation Create(string name, decimal value) 
     { 
      if (this.itemsInPool == 0) 
      { 
       // Block until new item ready - maybe use semaphore. 
       throw new NotImplementedException(); 
      } 

      // Items are in the pool, but no items have been created. 
      if (this.poolItems.Count == 0) 
      { 
       this.itemsInPool--; 
       return new StockQuotation(name, value); 
      } 

      // else, return one in the pool 
      this.itemsInPool--; 

      var item = this.poolItems[0]; 
      this.poolItems.Remove(item); 

      item.Name = name; 
      item.Value = value; 

      return item; 
     } 

     public void Release(StockQuotation quote) 
     { 
      if (!this.poolItems.Contains(quote) 
      { 
       this.poolItems.Add(quote); 
       this.itemsInPool++; 
      } 
     } 

    } 

das, dass die aktienkurse etwa wie folgt aussieht ist vorausgesetzt:

class StockQuotation 
    { 
     internal StockQuotation(string name, decimal value) 
     { 
      this.Name = name; 
      this.Value = value; 
     } 


     public string Name { get; set; } 
     public decimal Value { get; set; } 
    } 

Dann statt den Aufruf der neuen aktienkurse() Konstruktor, fragen Sie den Pool für eine neue Instanz Der Pool gibt eine vorhandene Instanz zurück (Sie können sie vordefinieren, wenn Sie möchten) und alle Eigenschaften so einstellen, dass sie wie eine neue Instanz aussieht.Möglicherweise müssen Sie so lange herumspielen, bis Sie eine Poolgröße gefunden haben, die groß genug ist, um die Threads gleichzeitig aufzunehmen.

So würden Sie es aus dem Thread nennen.

// Get the pool, maybe from a singleton. 
    var pool = new StockQuotationPool(100); 


    var quote = pool.Create("test", 1.00m); 


    try 
    { 
     // Work with quote 

    } 
    finally 
    { 
     pool.Release(quote); 
    } 

Schließlich ist diese Klasse im Moment nicht threadsicher. Lass es mich wissen, wenn du Hilfe brauchst.

+0

Schöne Idee, ich werde darüber nachdenken. Das Problem ist, dass, da dies vorher nicht in Betracht gezogen wurde, ich mir nicht sicher bin, welche Logik erforderlich ist, um zu wissen, wann das Release aufgerufen werden kann. –

+0

Ich habe den Code aktualisiert, um einen Versuch/schließlich zu enthalten. Sie möchten Objekte nicht verlieren, wenn Ausnahmen auftreten. –

+0

In Bezug darauf, wann pool.Release() aufgerufen wird, würde ich grundsätzlich sagen, kurz bevor es den Rahmen verlässt, wenn es in einer Methode ist. Wenn es in einem Feld eines Objekts gespeichert ist, können Sie das Objekt Disposable erstellen und Release() in der Dispose-Methode aufrufen. ** Je früher Sie es freigeben können, desto besser ** und desto kleiner muss Ihr Pool sein. –

Verwandte Themen