[C#] DataTable.SelectとLinqってどっちが早い?

DataTable.SelectメソッドとLinqの検索時間の比較を行いました。

今回は、DataTable.SelectメソッドとLinqの検索時間の比較を行いました。

Linqを使うと「Linq使うから処理が遅いんちゃうん」と、
処理が遅い原因だと言われていました。

でも、「DataTableは遅い…でも、Linqはもーっと遅い…」
っていうの、本当に正しいのでしょうか?

ということで、ちょっと探してみたところ、こんな記事を見つけてしまいました。

DataTableからのデータ抽出方法の性能比較(かずきのBlog@hatena)

もうすでに、記事で結果が出てしまっていますが、百聞は一見に如かず(?)
今回は、この記事のプログラムを元に実際に計測してみました。

また、Linqでは、この記事とは異なる書き方があるので、そちらとの比較してみています。

【検証環境】
CPU: Intel Core i7-4790 (3.60GHz x 2)
RAM: 16.0GB
OS:  Microsoft Windows 8.1
VS:  Microsoft Visual Studio Community 2013 Update 4
.NET: .NET Framework 4.5
言語: C#

まず、検索ですが、TEST No.1からNo.5でDataTable.SelectメソッドとLinqを比較しました。

[TEST No.1] – Selectメソッドによる検索

何も考えずに、table.Select(…)での検索を計測した結果です。

var rows = table.Select("COL_0 = 'DATA_10' OR COL_1 LIKE 'DATA_1%'""COL_0");

[TEST No.2] – インデックスを作ってSelectメソッドによる検索

インデックスを生成した後、table.Select(…)での検索を計測した結果です。

table.DefaultView.Sort = "COL_0, COL_1"; // <-この行の処理を計測に含まない
var rows = table.Select("COL_0 = 'DATA_10' OR COL_1 LIKE 'DATA_1%'""COL_0");

[TEST No.3] – インデックスを作ってSelectメソッドによる検索(インデックス生成含む)

インデックスの生成する前から、table.Select(…)で検索までを計測した結果です。

table.DefaultView.Sort = "COL_0, COL_1"; // <-この行の処理を計測に含む
var rows = table.Select("COL_0 = 'DATA_10' OR COL_1 LIKE 'DATA_1%'""COL_0");

[TEST No.4] – Linq to Data SetでSelect(その1)

from row in table.AsEnumerable()…での検索を計測した結果です。

var rows = (
                        from row in table.AsEnumerable()
                        let col0 = row.Field<string>("COL_0")
                        let col1 = row.Field<string>("COL_1")
                        where col0 == "DATA_10" || col1.StartsWith("DATA_1")
                        orderby col0
                        select row
                        ).ToArray();

[TEST No.5] – Linq to Data SetでSelect(その2)

table.AsEnumerable().Where(…)での検索を計測した結果です。

var rows = table.AsEnumerable()
                        .Where(g => (g.Field<string>("COL_0") == "DATA_10") || g.Field<string>("COL_1").StartsWith("DATA_1"))
                        .OrderBy(g => g.Field<string>("COL_0"))
                        .ToArray();

TEST No.6からは、インデックスの有無による書き込みの比較を行いました。

[TEST No.6] – インデックスを作らずにデータ書き込み

インデックスを生成せずに、データの上書きを行いました。

[TEST No.7] – インデックスを作ってデータ書き込み

インデックスを生成してから、データの上書きを行いました。

計測結果は、以下の通りでした。

■SELECTテスト結果
回数TEST No.1TEST No.2TEST No.3TEST No.4TEST
No.5
1回目534ms161ms684ms83ms85ms
2回目581ms153ms669ms96ms86ms
3回目565ms167ms691ms88ms79ms
4回目529ms159ms724ms96ms73ms
5回目543ms160ms662ms78ms72ms
集計550ms160ms686ms88ms79ms
■更新テスト結果
回数TEST No.6TEST No.7
1回目131ms1231ms
2回目114ms1346ms
3回目125ms1333ms
4回目115ms1330ms
5回目109ms586ms
集計119ms1165ms

(Excelのコピペで貼れるって、スゲー)

まず、DataTable.SelectとLinqとの比較結果ですが、差が広がりましたね…

また、件数が1000件程度と少ない場合、
両方とも早すぎてわからないぐらい(0ms(計測不可)~8msとか)なので、
やはり、Linqを使用したほうがよさそうです。

ただ、Linqでの比較でも、
「from row in table …」よりも、「table.AsEnumerable().Where(…)」の方が
約10msほど早く処理されることもわかりました。

(これで、Linqは遅いという迷信(?)は払拭された…?)

そして、書き込みの比較は、なんというか、酷いです….
書き込むたびにインデックスを更新しているのでしょうか…?

(その場合、インデックスの再生成を更新のたびに行っているので、この遅さは何となくわかるのですが)

というわけで、くじけずにこれからもLinqを使っていきたいと思います。

/*** 以下ソース全文 ***/

using System;
using System.Data;
using System.Diagnostics;
using System.Linq;
namespace DataTableVsLinq
{
    internal class Program
    {
#region Field
        /// <summary>
        /// 行数
        /// </summary>
        private const int COLUMN_NUM = 100000;
        /// <summary>
        /// 列数
        /// </summary>
        private const int ROW_NUM = 10;
#endregion
#region Method
        private static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("### TEST No.1 - Selectメソッドによる検索 ###");
                Console.WriteLine();
                TestNo1();
                Console.WriteLine();
                Console.WriteLine("### TEST No.2 - インデックスを作ってSelectメソッドによる検索 ###");
                Console.WriteLine();
                TestNo2();
                Console.WriteLine();
                Console.WriteLine("### TEST No.3 - インデックスを作ってSelectメソッドによる検索(インデックス生成含む) ###");
                Console.WriteLine();
                TestNo3();
                Console.WriteLine();
                Console.WriteLine("### TEST No.4 - Linq to Data SetでSelect(その1) ###");
                Console.WriteLine();
                TestNo4();
                Console.WriteLine();
                Console.WriteLine("### TEST No.5 - Linq to Data SetでSelect(その2) ###");
                Console.WriteLine();
                TestNo5();
                Console.WriteLine();
                Console.WriteLine("### TEST No.6 - 普通にデータ更新 ###");
                Console.WriteLine();
                TestNo6();
                Console.WriteLine();
                Console.WriteLine("### TEST No.7 - インデックスを作ってデータ更新 ###");
                Console.WriteLine();
                TestNo7();
                Console.WriteLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine("### Error! ###");
                Console.WriteLine(ex.ToString());
                Console.WriteLine("##############");
            }
            finally
            {
                Console.WriteLine("Finish!");
            }
        }
        /// <summary>
        /// TEST No.1 - Selectメソッドで検索する
        /// </summary>
        private static void TestNo1()
        {
            for (int i = 0; i < 5; i++)
            {
                var table = CreateDataTable(COLUMN_NUM, ROW_NUM);
                Watch(() =>
                {
                    var rows = table.Select("COL_0 = 'DATA_10' OR COL_1 LIKE 'DATA_1%'""COL_0");
                    Console.WriteLine("[検索] {0}行見つかりました", rows.Length);
                });
            }
        }
        /// <summary>
        /// TEST No.2 - インデックスを作ってSelect
        /// </summary>
        private static void TestNo2()
        {
            for (int i = 0; i < 5; i++)
            {
                var table = CreateDataTable(COLUMN_NUM, ROW_NUM);
                table.DefaultView.Sort = "COL_0, COL_1";
                Watch(() => 
                {
                    var rows = table.Select("COL_0 = 'DATA_10' OR COL_1 LIKE 'DATA_1%'""COL_0");
                    Console.WriteLine("[検索] {0}行見つかりました", rows.Length);
                });
            }
        }
        /// <summary>
        /// TEST No.3 - インデックスを作ってSelect(インデックス生成も含む)
        /// </summary>
        private static void TestNo3()
        {
            for (int i = 0; i < 5; i++)
            {
                var table = CreateDataTable(COLUMN_NUM, ROW_NUM);
                Watch(() =>
                {
                    table.DefaultView.Sort = "COL_0, COL_1";
                    var rows = table.Select("COL_0 = 'DATA_10' OR COL_1 LIKE 'DATA_1%'""COL_0");
                    Console.WriteLine("[検索] {0}行見つかりました", rows.Length);
                });
            }
        }
        /// <summary>
        /// TEST No.4 - Linq to DataSetで検索(その1)
        /// </summary>
        private static void TestNo4()
        {
            for (int i = 0; i < 5; i++)
            {
                var table = CreateDataTable(COLUMN_NUM, ROW_NUM);
                Watch(() =>
                {
                    var rows = (
                        from row in table.AsEnumerable()
                        let col0 = row.Field<string>("COL_0")
                        let col1 = row.Field<string>("COL_1")
                        where col0 == "DATA_10" || col1.StartsWith("DATA_1")
                        orderby col0
                        select row
                        ).ToArray();
                    Console.WriteLine("[検索] {0}行見つかりました", rows.Length);
                });
            }
        }
        /// <summary>
        /// TEST No.5 - Linq to DataSetで検索(その2:最近自分でやっているLinqの書き方)
        /// </summary>
        private static void TestNo5()
        {
            for (int i = 0; i < 5; i++)
            {
                var table = CreateDataTable(COLUMN_NUM, ROW_NUM);
                Watch(() =>
                {
                    var rows = table.AsEnumerable()
                        .Where(g => (g.Field<string>("COL_0") == "DATA_10") || g.Field<string>("COL_1").StartsWith("DATA_1"))
                        .OrderBy(g => g.Field<string>("COL_0"))
                        .ToArray();
                    Console.WriteLine("[検索] {0}行見つかりました", rows.Length);
                });
            }
        }
        /// <summary>
        /// TEST No.6 - インデックスを作らずにデータ書き込みを行う
        /// </summary>
        private static void TestNo6()
        {
            for (int i = 0; i < 5; i++)
            {
                var table = CreateDataTable(COLUMN_NUM, ROW_NUM);
                Watch(() => 
                {
                    foreach (var row in table.Rows.Cast<DataRow>())
                    {
                        row.BeginEdit();
                        foreach (var col in table.Columns.Cast<DataColumn>())
                            row[col] = "TEST";
                        row.EndEdit();
                    }
                });
            }
        }
        /// <summary>
        /// TEST No.7 - インデックスを作ってからデータ書き込みを行う
        /// </summary>
        private static void TestNo7()
        {
            for (int i = 0; i < 5; i++)
            {
                var table = CreateDataTable(COLUMN_NUM, ROW_NUM);
                table.DefaultView.Sort = "COL_0, COL_1";
                Watch(() =>
                {
                    foreach (var row in table.Rows.Cast<DataRow>())
                    {
                        row.BeginEdit();
                        foreach (var col in table.Columns.Cast<DataColumn>())
                            row[col] = "TEST";
                        row.EndEdit();
                    }
                });
            }
        }
        /// <summary>
        /// サンプルテーブルを生成します
        /// </summary>
        /// <param name="rowCount">行数</param>
        /// <param name="columnCount">列数</param>
        /// <returns></returns>
        private static DataTable CreateDataTable(int rowCount, int columnCount)
        {
            var dt = new DataTable("SampleTable");
            foreach (var column in Enumerable.Range(0, columnCount))
                dt.Columns.Add(string.Format("COL_{0}", column));
            var random = new Random();
            foreach (var row in Enumerable.Range(0, rowCount))
            {
                var newRow = dt.NewRow();
                foreach (var column in Enumerable.Range(0, columnCount))
                    newRow[column] = string.Format("DATA_{0}", random.Next(100));
                dt.Rows.Add(newRow);
            }
            return dt;
        }
        /// <summary>
        /// 処理時間を計測、出力します
        /// </summary>
        /// <param name="action">計測する処理</param>
        private static void Watch(Action action)
        {
            var watch = Stopwatch.StartNew();
            action();
            Console.WriteLine("[計測] 処理時間: {0}ms", watch.ElapsedMilliseconds);
            watch.Stop();
        }
#endregion
    }
}