none
DataTable.Select() Methode Klammern im select string ziehen die Performance in den Keller? RRS feed

  • Frage

  • Hallo Forum,

    aus purer Neugier gefragt, weil ich kürzlich beim Entwickeln darüber gestolpert bin:

    Was ist der Unterschied zwischenden beiden Aufrufen

    table.Select("[Field 03] = 'Val05000'");

    und

    table.Select("([Field 03] = 'Val05000')");

    der zu der gigantischen Performanceeinbusse führt?

    Gibt es ein Problem mit geklammerten Ausdrücken bei der Auswertung?

    Wer es nachvollziehen will hier ein Programmfragment, das eine Tabelle (5 Columns 100.000 Rows) anlegt und dann 10.000 mal einen select() Aufruf ausführt:

    class Program
        {
            private const int numOfColumns=5;
            static void Main(string[] args)
            {
                DataTable table = CreateTable();
                //table.WriteXml("testTable.xml");
              
                table.ReadXml("testTable.xml");
                Console.WriteLine(DateTime.Now.ToLongTimeString());
                for (int i = 0; i < 100000; i++)
                {

                    //table.Select("[Field 03] = 'Val05000'");
                    table.Select("([Field 03] = 'Val05000')");
                }
                Console.WriteLine(DateTime.Now.ToLongTimeString());
                System.Console.ReadLine();
            }

            static DataTable CreateTable()
            {
                DataTable dt = new DataTable("testTable");
                for (int i = 0; i < numOfColumns; i++)
                {
                    dt.Columns.Add(NewColumn(i));
                }
                //for (int i = 0; i < 100000; i++)
                //    dt.Rows.Add(CreateNewRow(dt.NewRow()));
                return dt;
            }

            private static DataColumn NewColumn(int i)
            {
                return new DataColumn("Field "+(100+i).ToString().Substring(1,2),typeof(string));
            }

            private static DataRow CreateNewRow(DataRow dr)
            {
                dr.ItemArray = CreateFieldValues();
                return dr;
            }

            private static string [] CreateFieldValues()
            {
                Random random = new Random();
                List<string> valueList = new List<string>();
                for (int i = 0; i < numOfColumns; i++)
                {
                    valueList.Add("Val" + (100000 + random.Next(10001)).ToString().Substring(1,5));
                }
                return valueList.ToArray();
            }
        }

    Dienstag, 8. Februar 2011 20:36

Antworten

  • Hallo,

    ja, da scheint es ein Problem zu geben.

    Ich habe Dein Beispiel mal auf das wesentliche reduziert:

    using System;
    using System.Data;
    using System.Diagnostics;
    
    namespace ElmarBoye.Samples.Data
    {
      class TestSelect
      {
        const int TableRowCount = 100000;
        const int DefaultLoopCount = 100; // 1000;
        const string FindColumn = "[Data]";
        const string FindValue = "Value 05000";
    
        private DataTable table;
        private int loopCount;
    
        internal TestSelect()
        {
          this.loopCount = DefaultLoopCount;
          this.table = CreateTable();
          Console.WriteLine("Table Rows: {0}", table.Rows.Count);
        }
    
        internal void Execute(int loopCount = DefaultLoopCount)
        {
          this.loopCount = loopCount;
          Console.WriteLine("Loops {0} times", this.loopCount);
          this.ExecuteSelect();
          this.ExecuteView();
        }
    
        #region Select
        internal void ExecuteSelect()
        {
          string binaryExpression = String.Format("{0} = '{1}'", FindColumn, FindValue);
          DoSelect(binaryExpression);
    
          string unaryExpression = String.Format("(" + binaryExpression + ")");
          DoSelect(unaryExpression);
        }
    
        private void DoSelect(string expression)
        {
          int count = 0;
          var watch = Stopwatch.StartNew();
          for (int index = 0; index < this.loopCount; index++)
          {
            // Sort doesn't help
            count += this.table.Select(expression, FindColumn).Length;
            //count += this.table.Select(expression, "").Length;
          }
          watch.Stop();
          Console.WriteLine("Select '{0}': {1} ms, Count: {2}", expression, watch.ElapsedMilliseconds, count);
        }
        #endregion Select
    
        #region Find By View
        internal void ExecuteView()
        {
          var view = new DataView(table, "", FindColumn, DataViewRowState.CurrentRows);
          DoView(view, FindValue);
        }
    
        private void DoView(DataView view, string findValue)
        {
          int count = 0;
          var watch = Stopwatch.StartNew();
          for (int index = 0; index < this.loopCount; index++)
          {
            count += view.FindRows(findValue).Length;
          }
          watch.Stop();
          Console.WriteLine("FindRows '{0}': {1} ms, Count: {2}", view.Sort, watch.ElapsedMilliseconds, count);
        }
        #endregion 
    
        static DataTable CreateTable()
        {
          DataTable table = new DataTable("TestTable");
          table.Columns.Add("Id", typeof(int));
          table.Columns.Add("Data", typeof(string));
    
          // table.PrimaryKey = new DataColumn[] { table.Columns["Id"] };
    
          var random = new Random(37);
          for (int index = 0; index < TableRowCount; index++)
            table.Rows.Add(index, String.Format("Value {0:00000}", random.Next(TableRowCount / 10)));
          table.AcceptChanges();
          return table;
        }
      }
    }
    
    


    Die Klammern führen dazu, dass die Optimierungen für das Suchkriterium nicht aktiv werden.
    Select verwendet wie auch die DataView intern eine DataExpression Klasse, die aus dem Filterausdruck
    in einen Ausdruck umwandelt. Um das Ganze zu beschleunigen, wird der Ausdruck auf bestimmte Bedingungen geprüft.
    So wird ein Ausdruck wie "Spalte = 'Wert'" besonders behandelt (über Index und ggf. binäre Suche) und ist so besonders schnell
    Dies gilt auch für eine Anzahl von anderen gängigen Ausdrücken.

    Durch die Klammern wird daraus ein unärer Ausdruck und sämtliche Optimierungen fallen unter den Tisch.
    Und die Daten werden darauf linear durchsucht. Was nebenbei beim 1. Mal sogar schneller ist, dann aber
    dramatisch abfällt (weswegen ich mich obern auf 100 Wiederholugen beschränkt habe).

    Wenn Du es Dir selbst anschauen willst, solltest Du das Source Debugging aktivieren.

    Moral von der Geschichte wäre hier:

    Die Ausdrücke möglichst einfach halten.

    Für wiederholte Suchen über die gleiche Spalte kann man eine DataView und FindRows verwenden -
    siehe oben ExecuteView.

    Wenn Du Dich damit nicht zufrieden geben willst, so wäre ein Connect-Eintrag eine Möglichkeit.
    Nur da es kein richtiger Fehler ist (die Zeilen werden ja geliefert, wenn auch tröpfchenweise),
    so wird es vermutlich keine schnelle Korrektur geben.

    Gruß Elmar

    • Als Antwort markiert contrequarte Donnerstag, 10. Februar 2011 07:02
    Mittwoch, 9. Februar 2011 13:58
    Beantworter

Alle Antworten

  • Hallo,

    ja, da scheint es ein Problem zu geben.

    Ich habe Dein Beispiel mal auf das wesentliche reduziert:

    using System;
    using System.Data;
    using System.Diagnostics;
    
    namespace ElmarBoye.Samples.Data
    {
      class TestSelect
      {
        const int TableRowCount = 100000;
        const int DefaultLoopCount = 100; // 1000;
        const string FindColumn = "[Data]";
        const string FindValue = "Value 05000";
    
        private DataTable table;
        private int loopCount;
    
        internal TestSelect()
        {
          this.loopCount = DefaultLoopCount;
          this.table = CreateTable();
          Console.WriteLine("Table Rows: {0}", table.Rows.Count);
        }
    
        internal void Execute(int loopCount = DefaultLoopCount)
        {
          this.loopCount = loopCount;
          Console.WriteLine("Loops {0} times", this.loopCount);
          this.ExecuteSelect();
          this.ExecuteView();
        }
    
        #region Select
        internal void ExecuteSelect()
        {
          string binaryExpression = String.Format("{0} = '{1}'", FindColumn, FindValue);
          DoSelect(binaryExpression);
    
          string unaryExpression = String.Format("(" + binaryExpression + ")");
          DoSelect(unaryExpression);
        }
    
        private void DoSelect(string expression)
        {
          int count = 0;
          var watch = Stopwatch.StartNew();
          for (int index = 0; index < this.loopCount; index++)
          {
            // Sort doesn't help
            count += this.table.Select(expression, FindColumn).Length;
            //count += this.table.Select(expression, "").Length;
          }
          watch.Stop();
          Console.WriteLine("Select '{0}': {1} ms, Count: {2}", expression, watch.ElapsedMilliseconds, count);
        }
        #endregion Select
    
        #region Find By View
        internal void ExecuteView()
        {
          var view = new DataView(table, "", FindColumn, DataViewRowState.CurrentRows);
          DoView(view, FindValue);
        }
    
        private void DoView(DataView view, string findValue)
        {
          int count = 0;
          var watch = Stopwatch.StartNew();
          for (int index = 0; index < this.loopCount; index++)
          {
            count += view.FindRows(findValue).Length;
          }
          watch.Stop();
          Console.WriteLine("FindRows '{0}': {1} ms, Count: {2}", view.Sort, watch.ElapsedMilliseconds, count);
        }
        #endregion 
    
        static DataTable CreateTable()
        {
          DataTable table = new DataTable("TestTable");
          table.Columns.Add("Id", typeof(int));
          table.Columns.Add("Data", typeof(string));
    
          // table.PrimaryKey = new DataColumn[] { table.Columns["Id"] };
    
          var random = new Random(37);
          for (int index = 0; index < TableRowCount; index++)
            table.Rows.Add(index, String.Format("Value {0:00000}", random.Next(TableRowCount / 10)));
          table.AcceptChanges();
          return table;
        }
      }
    }
    
    


    Die Klammern führen dazu, dass die Optimierungen für das Suchkriterium nicht aktiv werden.
    Select verwendet wie auch die DataView intern eine DataExpression Klasse, die aus dem Filterausdruck
    in einen Ausdruck umwandelt. Um das Ganze zu beschleunigen, wird der Ausdruck auf bestimmte Bedingungen geprüft.
    So wird ein Ausdruck wie "Spalte = 'Wert'" besonders behandelt (über Index und ggf. binäre Suche) und ist so besonders schnell
    Dies gilt auch für eine Anzahl von anderen gängigen Ausdrücken.

    Durch die Klammern wird daraus ein unärer Ausdruck und sämtliche Optimierungen fallen unter den Tisch.
    Und die Daten werden darauf linear durchsucht. Was nebenbei beim 1. Mal sogar schneller ist, dann aber
    dramatisch abfällt (weswegen ich mich obern auf 100 Wiederholugen beschränkt habe).

    Wenn Du es Dir selbst anschauen willst, solltest Du das Source Debugging aktivieren.

    Moral von der Geschichte wäre hier:

    Die Ausdrücke möglichst einfach halten.

    Für wiederholte Suchen über die gleiche Spalte kann man eine DataView und FindRows verwenden -
    siehe oben ExecuteView.

    Wenn Du Dich damit nicht zufrieden geben willst, so wäre ein Connect-Eintrag eine Möglichkeit.
    Nur da es kein richtiger Fehler ist (die Zeilen werden ja geliefert, wenn auch tröpfchenweise),
    so wird es vermutlich keine schnelle Korrektur geben.

    Gruß Elmar

    • Als Antwort markiert contrequarte Donnerstag, 10. Februar 2011 07:02
    Mittwoch, 9. Februar 2011 13:58
    Beantworter
  • Hallo Elmar,

    Danke für Dein Feedback.

    Wie gesagt es war mehr Neugier als ein echtes Problem, in unserem konkreten Fall können wir auch ohne Klammerungen leben. Persönlich mag ich Klammern, weil sie für mein Empfinden einen Ausdruck übersichtlicher gestalten.

    Nochmal danke für Deine Mühe

    contrequarte

    Donnerstag, 10. Februar 2011 07:17