今日は、ちょっくら調べ物でした。
そこで、気づいたのがDataTableとかって便利だけど使い方によっては遅いぞと。
有名どころでは、型指定でないDataSetで、DataTableからDataRowを取得してカラムのデータにアクセスするときに文字列で列名指定すると遅めだよというのがあります。
DataRowの列データへアクセスする方法と速度
ということで、DataRowのカラムのデータにアクセスするときに文字列でアクセスするパターンとDataColumnでアクセスするパターンとインデックスで指定するパターンを試してみました。
実験コード
using System;
using System.Data;
using System.Diagnostics;
using System.Text;
namespace DataTableSpeed
{
class Program
{
// 列数
private const int COLUMN_COUNT = 30;
// 行数
private const int ROW_COUNT = 50000;
static void Main(string[] args)
{
var dt = MakeDataTable();
AccessColumnName(dt);
AccessDataColumn(dt);
AccessIndex(dt);
}
// テスト用データテーブルを作成する
private static DataTable MakeDataTable()
{
var dt = new DataTable();
// カラム作成
for (int i = 0; i < COLUMN_COUNT; i++)
{
dt.Columns.Add("COL_" + i, typeof(string));
}
// 行データ追加
for (int i = 0; i < ROW_COUNT; i++)
{
var row = dt.NewRow();
foreach (DataColumn col in dt.Columns)
{
row[col] = col.ColumnName + "_" + i;
}
dt.Rows.Add(row);
}
return dt;
}
// 列名でアクセス
private static void AccessColumnName(DataTable dt)
{
var watch = new Stopwatch();
watch.Start();
foreach (DataRow row in dt.Rows)
{
var sb = new StringBuilder();
foreach (DataColumn col in dt.Columns)
{
// 文字列でアクセス!
sb.Append(row[col.ColumnName]);
}
}
watch.Stop();
Console.WriteLine("列名でアクセス: " + watch.ElapsedMilliseconds + "ms");
}
// 列名でアクセス
private static void AccessDataColumn(DataTable dt)
{
var watch = new Stopwatch();
watch.Start();
foreach (DataRow row in dt.Rows)
{
var sb = new StringBuilder();
foreach (DataColumn col in dt.Columns)
{
// DataColumnでアクセス
sb.Append(row[col]);
}
}
watch.Stop();
Console.WriteLine("DataColumnでアクセス: " + watch.ElapsedMilliseconds + "ms");
}
// インデックスでアクセス
private static void AccessIndex(DataTable dt)
{
var watch = new Stopwatch();
watch.Start();
foreach (DataRow row in dt.Rows)
{
var sb = new StringBuilder();
for (int i = 0; i < COLUMN_COUNT; i++)
{
// インデックスでアクセス
sb.Append(row[i]);
}
}
watch.Stop();
Console.WriteLine("インデックスでアクセス: " + watch.ElapsedMilliseconds + "ms");
}
}
}
結果
DataColumnとインデックスでのアクセスはまぁいいとして、列名でアクセスするときは倍くらい時間がかかってそうに見えます。
因みに型付DataSetを使うと以下のようなコードでDataRowにプロパティが定義されます。
public string Col1 {
get {
try {
return ((string)(this[this.tableDataTable1.Col1Column]));
}
catch (global::System.InvalidCastException e) {
throw new global::System.Data.StrongTypingException("テーブル \'DataTable1\' にある列 \'Col1\' の値は DBNull です。", e);
}
}
set {
this[this.tableDataTable1.Col1Column] = value;
}
}
ここのthis.tableDataTable1.Col1ColumnはDataColumnなので、DataColumnを使ったアクセスをしてくれます。
性能的にも生産性的にも型付DataSetが使えるシーンでは使っておくほうが無難だと思われます。
次!!
DataTable#Rows[index]の速度
これは知らなかった。
考えりゃ当然っちゃ当然な気もするけど、DataTable#Rows[index]へのアクセスも必要最低限にするようにすると早くなります。しかも件数が多いと結構効いてくる。
実験コード
using System;
using System.Data;
using System.Diagnostics;
using System.Text;
namespace DataTableSpeed
{
class Program
{
// 列数
private const int COLUMN_COUNT = 30;
// 行数
private const int ROW_COUNT = 50000;
static void Main(string[] args)
{
var dt = MakeDataTable();
SlowIndexAccess(dt);
SmartIndexAccess(dt);
ForEachAccess(dt);
}
// テスト用データテーブルを作成する
private static DataTable MakeDataTable()
{
var dt = new DataTable();
// カラム作成
for (int i = 0; i < COLUMN_COUNT; i++)
{
dt.Columns.Add("COL_" + i, typeof(string));
}
// 行データ追加
for (int i = 0; i < ROW_COUNT; i++)
{
var row = dt.NewRow();
foreach (DataColumn col in dt.Columns)
{
row[col] = col.ColumnName + "_" + i;
}
dt.Rows.Add(row);
}
return dt;
}
// 非効率な感じ
private static void SlowIndexAccess(DataTable dt)
{
var watch = new Stopwatch();
watch.Start();
for (int row = 0; row < ROW_COUNT; row++)
{
var sb = new StringBuilder();
for (int col = 0; col < COLUMN_COUNT; col++)
{
// ループ内で毎回Rowsを使ってアクセス
sb.Append(dt.Rows[row][col]);
}
}
watch.Stop();
Console.WriteLine("ループ内で毎回Rowsを使ってアクセス: " + watch.ElapsedMilliseconds + "ms");
}
// 効率的な感じ
private static void SmartIndexAccess(DataTable dt)
{
var watch = new Stopwatch();
watch.Start();
for (int row = 0; row < ROW_COUNT; row++)
{
// 一度だけRowsにアクセスする
var dataRow = dt.Rows[row];
var sb = new StringBuilder();
for (int col = 0; col < COLUMN_COUNT; col++)
{
// daraRow変数を使いまわす
sb.Append(dataRow[col]);
}
}
watch.Stop();
Console.WriteLine("必要最低限のRowsへのアクセス: " + watch.ElapsedMilliseconds + "ms");
}
// まかせる
private static void ForEachAccess(DataTable dt)
{
var watch = new Stopwatch();
watch.Start();
// foreachによるアクセス
foreach (DataRow dataRow in dt.Rows)
{
var sb = new StringBuilder();
for (int col = 0; col < COLUMN_COUNT; col++)
{
// daraRow変数を使いまわす
sb.Append(dataRow[col]);
}
}
watch.Stop();
Console.WriteLine("foreachでアクセス: " + watch.ElapsedMilliseconds + "ms");
}
}
}
実行結果
Selectメソッドによる検索
これは当然。汎用的なメソッド程遅いのが道理。
どれくらい違うのか試してみました。
実験コード
using System;
using System.Data;
using System.Diagnostics;
using System.Text;
using System.Linq;
using System.Collections.Generic;
namespace DataTableSpeed
{
class Program
{
// 列数
private const int COLUMN_COUNT = 30;
// 行数
private const int ROW_COUNT = 50000;
static void Main(string[] args)
{
var dt = MakeDataTable();
UseSelect(dt);
UseLinq(dt);
UseLoop(dt);
}
// テスト用データテーブルを作成する
private static DataTable MakeDataTable()
{
var dt = new DataTable();
// カラム作成
for (int i = 0; i < COLUMN_COUNT; i++)
{
dt.Columns.Add("COL_" + i, typeof(string));
}
// 行データ追加
for (int i = 0; i < ROW_COUNT; i++)
{
var row = dt.NewRow();
foreach (DataColumn col in dt.Columns)
{
row[col] = col.ColumnName + "_" + i;
}
dt.Rows.Add(row);
}
return dt;
}
private static void UseSelect(DataTable dt)
{
var watch = new Stopwatch();
watch.Start();
// COL_1の値がCOL_1_10000の列が欲しいねん
var ret = dt.Select("COL_1 = 'COL_1_10000'");
watch.Stop();
Console.WriteLine("Selectで検索: " + watch.ElapsedMilliseconds + "ms");
}
private static void UseLinq(DataTable dt)
{
var watch = new Stopwatch();
watch.Start();
// COL_1の値がCOL_1_10000の列が欲しいねん
var col1 = dt.Columns["COL_1"];
var ret = dt.AsEnumerable().Where(row => (string)row[col1] == "COL_1_10000").ToArray();
watch.Stop();
Console.WriteLine("Linqで検索: " + watch.ElapsedMilliseconds + "ms");
}
private static void UseLoop(DataTable dt)
{
var watch = new Stopwatch();
watch.Start();
// COL_1の値がCOL_1_10000の列が欲しいねん
var col1 = dt.Columns["COL_1"];
var list = new List<DataRow>();
foreach (DataRow row in dt.Rows)
{
if ((string)row[col1] == "COL_1_10000")
{
list.Add(row);
}
}
var ret = list.ToArray();
watch.Stop();
Console.WriteLine("ループで検索: " + watch.ElapsedMilliseconds + "ms");
}
}
}
実行結果
ということで、Selectがダントツで遅いです。
いっぱつ限りならいいですが、ループ内でSelectを使って検索を繰り返すときはLinqかループでごりっとやっちゃいましょう。
ということを今日体感しました。