DataTable.Select vs List<T>.Any vs LINQ To DataTable
От: Berill Азербайджан  
Дата: 02.05.16 22:32
Оценка:
Доброго всем.
В данный момент переписываю метод , в котором используются DataSet'ы и DataTable'ы.
Суть простая. Из БД получаем некий начальный курсор данных (DataSet). Далее в методе происходит добавление строк к DataSet.Tables[0] при выполнении определенных условий. При этом для исключения дублирования строк применяется DataTable.Select(). Код выглядит примерно так:
string filter = string.Format("TYPE_CODE={0} AND DETAIL_ID={1}", addingRowInfo.TypeCode, addingRowInfo.DetailID);
if (dataSet.Tables[0].Select(filter).Length == 0)
{
    // добавление новой строки в dataSet.Tables[0]
}
И так пару десятков раз для каждого типа добавляемой строки (фильтр может быть различным).

Я пришел к выводу, что мне, по сути, нужен всего лишь List<T> вместо DataTable'а (я уж молчу про DataSet). Я задался вопросом: насколько быстро будет работа с List<T>.Any по сравнению с DataTable.Select.

  Код
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;

namespace DataTableSelect
{
    static class DataTableExtensions
    {
        public static DataTable ToDataTable<T>(this IList<T> data)
        {
            PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
            DataTable table = new DataTable();
            foreach ( PropertyDescriptor prop in properties )
            {
                table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
            }

            foreach ( T item in data )
            {
                DataRow row = table.NewRow();
                foreach ( PropertyDescriptor prop in properties )
                {
                    row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
                }
                table.Rows.Add(row);
            }
            return table;
        }
    }

    public class MyData
    {
        public int Code { get; set; }
        public string Period { get; set; }
        public int TypeCode { get; set; }
        public int DetailID { get; set; }

        public override string ToString()
        {
            return string.Format("Code: {0}; Period: {1}; TypeCode: {2}; DetailID: {3};", Code, Period, TypeCode, DetailID);
        }
    }

    class Program
    {
        static void Main()
        {
            Console.WindowWidth = 120;

            int count = 1 * 1000;
            List<MyData> myList = CreateTestList();
            DataTable table = myList.ToDataTable();
            GetResultsForList(count, myList);
            GetResultsForTableWithLINQ(count, table);
            GetResultsForTableWithSelect(count, table);

            count = 2 * 1000;
            GetResultsForList(count, myList);
            GetResultsForTableWithLINQ(count, table);
            GetResultsForTableWithSelect(count, table);

            count = 4 * 1000;
            GetResultsForList(count, myList);
            GetResultsForTableWithLINQ(count, table);
            GetResultsForTableWithSelect(count, table);

            count = 8 * 1000;
            GetResultsForList(count, myList);
            GetResultsForTableWithLINQ(count, table);
            GetResultsForTableWithSelect(count, table);

            count = 16 * 1000;
            GetResultsForList(count, myList);
            GetResultsForTableWithLINQ(count, table);
            GetResultsForTableWithSelect(count, table);

            count = 100 * 1000;
            GetResultsForList(count, myList);
            GetResultsForTableWithLINQ(count, table);
            GetResultsForTableWithSelect(count, table);

            Console.WriteLine("Done...");
            Console.ReadLine();
        }

        private static void GetResultsForList(int count, List<MyData> myList)
        {
            Measure("List",
                    () =>
                    {
                        for ( int i = 0; i < count; i++ )
                        {
                            foreach ( var myData in myList )
                            {
                                if ( myData.Period == "01.01.2016" && myData.DetailID == 10 )
                                {
                                    break;
                                }
                            }
                        }
                        return count;
                    });
        }

        private static void GetResultsForTableWithLINQ(int count, DataTable table)
        {
            Measure("Table LINQ",
                    () =>
                    {
                        for ( int i = 0; i < count; i++ )
                        {
                            var q = table.AsEnumerable().Where(row => row.Field<string>("Period") == "01.01.2016" && row.Field<int>("DetailID") == 10);
                            var found = q.ToList().Count != 0;
                        }
                        return count;
                    });
        }

        private static void GetResultsForTableWithSelect(int count, DataTable table)
        {
            string filter2 = "Period='01.01.2016' AND DetailID=10";

            Measure("Table Select",
                    () =>
                    {
                        for ( int i = 0; i < count; i++ )
                        {
                            var found = table.Select(filter2).Length != 0;

                        }
                        return count;
                    });
            Console.WriteLine("=========================================================================");
        }

        private static List<MyData> CreateTestList()
        {
            List<MyData> foos = new List<MyData>();
            for ( int i = 0; i < 20; i++ )
            {
                foos.Add(new MyData
                {
                    Code = i,
                    TypeCode = i,
                    Period = i == 10 ? "01.01.2016" : string.Empty,
                    DetailID = i == 10 ? 10 : 0
                });
            }

            return foos;
        }

        static void Measure(string name, Func<long> callback)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            var mem = GC.GetTotalMemory(true);
            var gc00 = GC.CollectionCount(0);
            var gc01 = GC.CollectionCount(1);
            var gc02 = GC.CollectionCount(2);

            var sw = Stopwatch.StartNew();
            var result = callback();
            sw.Stop();

            var mem2 = GC.GetTotalMemory(false);
            var gc10 = GC.CollectionCount(0);
            var gc11 = GC.CollectionCount(1);
            var gc12 = GC.CollectionCount(2);

            var memDelta = (mem2 - mem) / 1024.0;
            var gcDelta0 = gc10 - gc00;
            var gcDelta1 = gc11 - gc01;
            var gcDelta2 = gc12 - gc02;

            Console.WriteLine(
                "{0,25}: {1,5}ms, ips: {2,22:N} | Mem: {3,6:N2} kb, GC 0/1/2: {4}/{5}/{6} => {7,6}",
                name, sw.ElapsedMilliseconds, result / sw.Elapsed.TotalSeconds, memDelta, gcDelta0, gcDelta1, gcDelta2, result);
        }
    }
}


  Результаты
                     List:    26ms, ips:              37 057.49 | Mem:   8.00 kb, GC 0/1/2: 0/0/0 =>   1000
               Table LINQ:    69ms, ips:              14 329.03 | Mem: 454.95 kb, GC 0/1/2: 0/0/0 =>   1000
             Table Select:    47ms, ips:              21 209.94 | Mem: 1 681.93 kb, GC 0/1/2: 0/0/0 =>   1000
=========================================================================
                     List:    51ms, ips:              38 831.33 | Mem:   8.00 kb, GC 0/1/2: 0/0/0 =>   2000
               Table LINQ:    89ms, ips:              22 231.16 | Mem: 912.00 kb, GC 0/1/2: 0/0/0 =>   2000
             Table Select:    17ms, ips:             113 244.51 | Mem: 280.59 kb, GC 0/1/2: 1/0/0 =>   2000
=========================================================================
                     List:   101ms, ips:              39 299.02 | Mem:   8.00 kb, GC 0/1/2: 0/0/0 =>   4000
               Table LINQ:   180ms, ips:              22 202.92 | Mem: 1 816.00 kb, GC 0/1/2: 0/0/0 =>   4000
             Table Select:    34ms, ips:             116 006.93 | Mem: 562.63 kb, GC 0/1/2: 2/0/0 =>   4000
=========================================================================
                     List:   202ms, ips:              39 581.31 | Mem:   8.00 kb, GC 0/1/2: 0/0/0 =>   8000
               Table LINQ:   355ms, ips:              22 495.04 | Mem: 560.66 kb, GC 0/1/2: 1/0/0 =>   8000
             Table Select:    68ms, ips:             117 283.31 | Mem: 1 123.83 kb, GC 0/1/2: 4/0/0 =>   8000
=========================================================================
                     List:   403ms, ips:              39 649.04 | Mem:   8.00 kb, GC 0/1/2: 0/0/0 =>  16000
               Table LINQ:   712ms, ips:              22 464.26 | Mem: 1 113.33 kb, GC 0/1/2: 2/0/0 =>  16000
             Table Select:   138ms, ips:             115 749.28 | Mem: 2 249.10 kb, GC 0/1/2: 8/0/0 =>  16000
=========================================================================
                     List:  2530ms, ips:              39 510.26 | Mem:   8.00 kb, GC 0/1/2: 0/0/0 => 100000
               Table LINQ:  4459ms, ips:              22 424.43 | Mem: 2 320.98 kb, GC 0/1/2: 14/0/0 => 100000
             Table Select:   864ms, ips:             115 615.97 | Mem: 1 738.18 kb, GC 0/1/2: 54/0/0 => 100000
=========================================================================
Done...


Не ожидал таких результатов. Думал, что работать со списком будет быстрее. Может я что-то не так делаю?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.