2015-01-30 9 views
6

Werfen Sie einen Blick auf das folgende Programm. Es ist ziemlich selbsterklärend, aber ich werde es trotzdem erklären :)Extremer Leistungsunterschied bei der Verwendung von DataTable.Add

Ich habe zwei Methoden, eine schnell und eine langsam. Diese Methoden machen genau das Gleiche: Sie erstellen eine Tabelle mit 50.000 Zeilen und 1000 Spalten. Ich schreibe in eine variable Anzahl von Spalten in der Tabelle. Im folgenden Code habe ich 10 (NUM_COLS_TO_WRITE_TO) ausgewählt.

Mit anderen Worten, nur 10 Spalten von 1000 werden tatsächlich Daten enthalten. OK. Der nur Unterschied zwischen den beiden Methoden ist, dass das schnelle füllt die Spalten und dann Anrufe DataTable.AddRow, während die langsame macht es nach. Das ist es.

Der Leistungsunterschied ist jedoch schockierend (für mich jedenfalls). Die schnelle Version ist fast vollständig unabhängig von der Anzahl der Spalten, in die wir schreiben, während die langsame linear ansteigt. Wenn zum Beispiel die Anzahl der Spalten, in die ich schreibe, 20 beträgt, dauert die schnelle Version 2,8 Sekunden, aber die langsame Version übernimmt eine Minute.

Was in der Welt könnte hier möglicherweise vor sich gehen?

dachte ich, dass vielleicht dt.BeginLoadData Zugabe einen Unterschied machen würde und es zu einem gewissen Grad hat, ist es die Zeit gebracht von 61 Sekunden bis ca. 50 Sekunden, aber das ist immer noch ein großer Unterschied.

Natürlich ist die offensichtliche Antwort: "Nun, mach es nicht so." OK. Sicher. Aber was in der Welt verursacht das? Ist das erwartetes Verhalten? Ich habe es nicht erwartet. :)

public class Program 
{ 
    private const int NUM_ROWS = 50000; 
    private const int NUM_COLS_TO_WRITE_TO = 10; 
    private const int NUM_COLS_TO_CREATE = 1000; 

    private static void AddRowFast() { 
     DataTable dt = new DataTable();    
     //add a table with 1000 columns 
     for (int i = 0; i < NUM_COLS_TO_CREATE; i++) { 
      dt.Columns.Add("x" + i, typeof(string)); 
     } 
     for (int i = 0; i < NUM_ROWS; i++) {     
      var theRow = dt.NewRow(); 
      for (int j = 0; j < NUM_COLS_TO_WRITE_TO; j++) { 
       theRow[j] = "whatever"; 
      } 

      //add the row *after* populating it 
      dt.Rows.Add(theRow);     
     } 
    } 

    private static void AddRowSlow() { 
     DataTable dt = new DataTable(); 
     //add a table with 1000 columns 
     for (int i = 0; i < NUM_COLS_TO_CREATE; i++) { 
      dt.Columns.Add("x" + i, typeof(string)); 
     } 
     for (int i = 0; i < NUM_ROWS; i++) { 
      var theRow = dt.NewRow(); 
      //add the row *before* populating it 
      dt.Rows.Add(theRow); 

      for (int j=0; j< NUM_COLS_TO_WRITE_TO; j++){ 
       theRow[j] = "whatever"; 
      }     
     } 
    } 

    static void Main(string[] args) 
    { 
     var sw = Stopwatch.StartNew(); 
     AddRowFast(); 
     sw.Stop(); 
     Console.WriteLine(sw.Elapsed.TotalMilliseconds); 

     sw.Restart(); 
     AddRowSlow(); 
     sw.Stop(); 
     Console.WriteLine(sw.Elapsed.TotalMilliseconds); 

     //When NUM_COLS is 5 
     //FAST: 2754.6782 
     //SLOW: 15794.1378 

     //When NUM_COLS is 10 
     //FAST: 2777.431 ms 
     //SLOW 32004.7203 ms 

     //When NUM_COLS is 20 
     //FAST: 2831.1733 ms 
     //SLOW: 61246.2243 ms 
    } 
} 

aktualisiert

Aufruf theRow.BeginEdit und theRow.EndEdit in der langsamen Version macht die langsame Version mehr oder weniger konstant (~ 4 Sekunden auf meinem Rechner). Wenn ich tatsächlich hatte einige Einschränkungen auf dem Tisch, ich denke, das könnte für mich sinnvoll sein.

+1

Ich denke, Erklärung ist ziemlich einfach. Wenn 'DataRow' außerhalb der Tabelle liegt, löst das Ändern von Daten keine Prüfungen/Verifizierungen/Benachrichtigungen usw. aus. Um eine genauere Erklärung zu erhalten, würde ich' DataRow's Spaltenwerteinstellungs-Code betrachten ... – Dennis

+0

@Dennis, aber ich dachte Der Zweck von BeginLoadData war es, "Benachrichtigungen, Indexwartung und Einschränkungen beim Laden von Daten auszuschalten". Außerdem scheint es, als wäre etwas 30 * mal * langsamer ein ziemlich großer Unterschied. – aquinas

+0

Außerdem scheint mir seltsam, dass die Anzahl der Spalten in der Tabelle die Laufzeit beeinflusst. Das heißt, wenn ich 10 Spalten aktualisiere und da nur * 10 Spalten sind, dann läuft es völlig in Ordnung. Also, was macht .NET mit all diesen Spalten, die ich nie anfasse? Schießen Ereignisse auch auf diese? Scheint seltsam. – aquinas

Antwort

5

Wenn an die Tabelle angehängt ist, wird viel mehr daran gearbeitet, den Status bei jeder Änderung aufzuzeichnen und zu verfolgen.

Zum Beispiel, wenn Sie dies tun,

theRow.BeginEdit(); 

for (int j = 0; j < NUM_COLS_TO_WRITE_TO; j++) 
{ 
    theRow[j] = "whatever"; 
} 

theRow.CancelEdit(); 

Dann in BeginEdit(), internally es eine Kopie des Inhalts der Reihe nehmen, so dass zu jedem Punkt, den Sie Rollback kann - und das Endergebnis das obige ist wieder eine leere Zeile ohne whatever. Dies ist auch im BeginLoadData Modus noch möglich. Nach dem Pfad von BeginEditwenn Sie an eine DataTable angeschlossen sind, schließlich kommen Sie in DataTable.NewRecord(), die zeigt, dass es nur jeden Wert für jede Spalte kopiert, um den ursprünglichen Zustand zu speichern, wenn ein Abbrechen erforderlich ist - nicht viel Magie hier. Auf der anderen Seite, wenn nicht an eine Datentabelle angehängt, passiert nicht viel in BeginEdit überhaupt und es wird schnell beendet.

EndEdit() ist in ähnlicher Weise ziemlich schwer (wenn angehängt), da hier alle Einschränkungen usw. überprüft werden (maximale Länge, Spalte erlaubt Nullen usw.).Außerdem löst es eine Reihe von Ereignissen aus, befreit explizit den Speicher, der verwendet wurde, wenn die Bearbeitung abgebrochen wurde, und stellt sie mit DataTable.GetChanges() zur Verfügung, was in BeginLoadData noch möglich ist. Infact Quelle betrachten alle BeginLoadData scheint zu deaktivieren Constraint-Prüfung und Indizierung.

So beschreibt, was BeginEdit und EditEdit tut, und sie sind völlig unterschiedlich, wenn angebracht oder nicht in Bezug auf was gespeichert ist. Nun bedenken Sie, dass eine einzige theRow[j] = "whatever" Sie auf der Indexer-Setter für DataRowBeginEditInternal und dann EditEdit bei jedem einzelnen Aufruf (wenn es nicht bereits in einer Bearbeitung ist, weil Sie zuvor explizit BeginEdit aufgerufen haben). Das bedeutet, dass jedes Mal, wenn Sie diesen Aufruf ausführen, jeder einzelne Wert für jede Spalte in der Zeile kopiert und gespeichert wird. Sie tun es also 10 Mal, das bedeutet mit Ihrer 1.000 Spalte DataTable, über 50.000 Zeilen, dass es 500.000.000 Objekte zuweist. Darüber hinaus werden alle anderen Versionierungen, Prüfungen und Ereignisse nach jeder einzelnen Änderung ausgelöst, und insgesamt ist es viel langsamer, wenn die Zeile an eine DataTable angehängt wird, als wenn nicht.

+0

Gute Antwort. Ja, der Anruf bei NewRecord ist definitiv ein Mörder. – aquinas

Verwandte Themen