今回は、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
///
/// 行数
///
private const int COLUMN_NUM = 100000;
///
/// 列数
///
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!");
}
}
///
/// TEST No.1 - Selectメソッドで検索する
///
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);
});
}
}
///
/// TEST No.2 - インデックスを作ってSelect
///
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);
});
}
}
///
/// TEST No.3 - インデックスを作ってSelect(インデックス生成も含む)
///
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);
});
}
}
///
/// TEST No.4 - Linq to DataSetで検索(その1)
///
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);
});
}
}
///
/// TEST No.5 - Linq to DataSetで検索(その2:最近自分でやっているLinqの書き方)
///
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);
});
}
}
///
/// TEST No.6 - インデックスを作らずにデータ書き込みを行う
///
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();
}
});
}
}
///
/// TEST No.7 - インデックスを作ってからデータ書き込みを行う
///
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();
}
});
}
}
///
/// サンプルテーブルを生成します
///
/// "rowCount">行数
/// "columnCount">列数
///
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;
}
///
/// 処理時間を計測、出力します
///
/// "action">計測する処理
private static void Watch(Action action)
{
var watch = Stopwatch.StartNew();
action();
Console.WriteLine("[計測] 処理時間: {0}ms", watch.ElapsedMilliseconds);
watch.Stop();
}
#endregion
}
}