今回は、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.1 TEST No.2 TEST No.3 TEST No.4 TEST
No.5
1回目 534ms 161ms 684ms 83ms 85ms
2回目 581ms 153ms 669ms 96ms 86ms
3回目 565ms 167ms 691ms 88ms 79ms
4回目 529ms 159ms 724ms 96ms 73ms
5回目 543ms 160ms 662ms 78ms 72ms
集計 550ms 160ms 686ms 88ms 79ms
■更新テスト結果
回数 TEST No.6 TEST No.7
1回目 131ms 1231ms
2回目 114ms 1346ms
3回目 125ms 1333ms
4回目 115ms 1330ms
5回目 109ms 586ms
集計 119ms 1165ms

(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
 = "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
 = "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     } }