はじめまして、エンジニアのMasashiです。普段はC#で業務系アプリの製造を行っています。
C#を扱っているとポピュラーなDataTableですが、調べてみると処理速度が遅いという記事がよく見られます。
今回自分は、以前から記事を見て気になっていましたDataTableのデータアクセス速度について、いくつかパターンを設けて調査を行ってみました。
測定までの流れ
今回測定を行うのは、DataTableまたは、Listに格納されているデータに全件アクセスする際の速度になっています。
DataTableについては、データへのアクセス方法にいくつか手段を持たせ、各々の速度を測定してみます。
Listのデータアクセス速度を測定する理由は、DataTableと同件数のデータへアクセスする際の速度比較用になります。
測定環境
- Visual Studio 2017
対象データ数
- 列数:100
- 行数:100000
測定対象
- DataTableに列名でアクセス
- DataTableにDataColumnでアクセス
- DataTableのDataColumnにIndexを使用してアクセス
- DataTableのRow・ColumnともにIndexを使用してアクセス
- Listにアクセス
テストコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Text; namespace DtListSpeedComparison { class Program { // 列数 const int COLUMN_COUNT = 100; // 行数 const int ROW_COUNT = 100000; static void Main(string[] args) { var dt = MakeDataTable(); var list = MakeList(); for(var i = 0; i < 10; i++) { Console.WriteLine("----------------"); ColumnNameAccess(dt); DataColumnAccess(dt); DataColumnIndexAccess(dt); DataRowColumnIndexAccess(dt); ListAccess(list); Console.WriteLine("----------------"); } Console.WriteLine("続行するには何かキーを押してください:"); Console.ReadKey(); } // 列名でアクセス private static void ColumnNameAccess(DataTable dt) { var stopwatch = new Stopwatch(); stopwatch.Start(); var sb = new StringBuilder(); foreach(DataRow dr in dt.Rows) { foreach(DataColumn dc in dt.Columns) { // 列名で取得 sb.Append(dr[dc.ColumnName]); } } stopwatch.Stop(); Console.WriteLine("計測時間【列名】: " + stopwatch.ElapsedMilliseconds + "ms"); } // DataColumnでアクセス private static void DataColumnAccess(DataTable dt) { var stopwatch = new Stopwatch(); stopwatch.Start(); var sb = new StringBuilder(); foreach (DataRow dr in dt.Rows) { foreach (DataColumn dc in dt.Columns) { // DataColumnで取得 sb.Append(dr[dc]); } } stopwatch.Stop(); Console.WriteLine("計測時間【DataColumn】: " + stopwatch.ElapsedMilliseconds + "ms"); } // DataColumnのみIndexを使用してアクセス private static void DataColumnIndexAccess(DataTable dt) { var stopwatch = new Stopwatch(); stopwatch.Start(); var sb = new StringBuilder(); foreach (DataRow dr in dt.Rows) { for(var i = 0; i < COLUMN_COUNT; i++) { // DataColumnのみIndexでアクセス sb.Append(dr[i]); } } stopwatch.Stop(); Console.WriteLine("計測時間【列Index】: " + stopwatch.ElapsedMilliseconds + "ms"); } // Row/ColumnともにIndexを使用してアクセス private static void DataRowColumnIndexAccess(DataTable dt) { var stopwatch = new Stopwatch(); stopwatch.Start(); var sb = new StringBuilder(); for(var i = 0; i < ROW_COUNT; i++) { var row = dt.Rows[i]; for(var j = 0; j < COLUMN_COUNT; j++) { // Row/ColumnともにIndexでアクセス sb.Append(row[j]); } } stopwatch.Stop(); Console.WriteLine("計測時間【行列Index】: " + stopwatch.ElapsedMilliseconds + "ms"); } // Listでデータ取得 private static void ListAccess(List<object[]> list) { var stopwatch = new Stopwatch(); stopwatch.Start(); var sb = new StringBuilder(); for (var i = 0; i < ROW_COUNT; i++) { var row = list[i]; for (var j = 0; j < COLUMN_COUNT; j++) { sb.Append(row[j]); } } stopwatch.Stop(); Console.WriteLine("計測時間【List】: " + stopwatch.ElapsedMilliseconds + "ms"); } // テストデータ作成(DataTable) private static DataTable MakeDataTable() { var dt = new DataTable(); for(var i = 0; i < COLUMN_COUNT; i++) { dt.Columns.Add("Column" + i, typeof(string)); } for(var i = 0; i < ROW_COUNT; i++) { var dr = dt.NewRow(); foreach(DataColumn dc in dt.Columns) { dr[dc] = dc.ColumnName + "_Row" + i; } dt.Rows.Add(dr); } return dt; } // テストデータ作成(List) private static List<object[]> MakeList() { var list = new List<object[]>(); for(var i = 0; i < ROW_COUNT; i++) { var row = new object[COLUMN_COUNT]; list.Add(row); } for(var i = 0; i < ROW_COUNT; i++) { for(var j = 0; j < COLUMN_COUNT; j++) { list[i][j] = "Column" + j + "_Row" + i; } } return list; } } } |
測定結果
10回試行した結果の平均値が下記になっています。小数点以下は四捨五入しています。
計測項目 | 計測時間(ms) |
---|---|
列名でアクセス | 1332 |
DataColumnでアクセス | 1067 |
DataColumnのみIndexを使用 | 923 |
Row/ColumnともにIndexを使用 | 1026 |
Listにアクセス | 784 |
上記からDataTableでのデータ取得速度を比較するとDataColumnにIndexを適用したパターンが一番速い結果になりました。DataTableを使用する際に一番使用している列名でのアクセスは考えていた通り一番遅く、Listと比較すると約1.7倍の速度差があることがわかります。
DataColumnにIndexを使用したデータとRow/Column共にIndexを使用しているデータで速度差があるのは、Rowデータを使いまわすために行単位で一度変数に格納している処理が加わっているためではないかと考えられます。
まとめ
DataTableのデータにアクセスする際は、DataColumnを使用するかIndexを使用してアクセスすることにより、列名でアクセスするよりも速度が向上しました。しかし、可読性等を考えるとやはり、列名でアクセスする方法に軍配が上がるかと思います。
アクセス速度の向上が求められる場合は、DataColumnやIndexでアクセスまたは、Listに変換してアクセスする方法を検討してみてはいかがでしょうか。
機会がありましたらDataTableのデータ抽出処理の速度比較も調査してみようと思います。