今回は、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 } }