DataTable からデータ抽出方法の性能比較 on .NET 6, 8 and 9
3 年前くらいに同じことを .NET 6 でやりました。
今回は、それを .NET 8 と .NET 9 も追加してやってみます。.NET 9 は RC2 を使っています。
ベンチマークのリポジトリ のコードを少し書き換えて .NET 6 に加えて .NET 8 と .NET 9 を追加しました。
コードは以下のようになります。
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System.Data;
namespace DataTableBenchmark;
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net48, baseline: true)]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net60)]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net80)]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net90)]
[MemoryDiagnoser]
public class DataTableBenchmarks
{
private int _rowCount = 100000;
private int _columnCount = 100;
private DataTable _dataTable;
[IterationSetup(Targets = new[] { nameof(SelectMethod), nameof(LinqToDataSet), nameof(CreateIndexAndSelectMethod) })]
public void Setup()
{
_dataTable = CreateDataTable();
}
[IterationSetup(Target = nameof(SelectMethodWithIndex))]
public void SetupForIndex()
{
_dataTable = CreateDataTable();
_dataTable.DefaultView.Sort = "COLUMN_0, COLUMN_1";
}
private DataTable CreateDataTable()
{
var dataTable = new DataTable("DummyTable");
// ダミー列の作成 COLUMN_0 〜 COLUMN_ColumnCountまで作る
foreach (var column in Enumerable.Range(0, _columnCount))
{
dataTable.Columns.Add($"COLUMN_{column}");
}
// ダミーデータの作成
foreach (var row in Enumerable.Range(0, _rowCount))
{
var rowData = dataTable.NewRow();
foreach (var column in Enumerable.Range(0, _columnCount))
{
rowData[column] = $"DATA_{(row + 1) * (column + 1) % 100}";
}
dataTable.Rows.Add(rowData);
}
return dataTable;
}
[Benchmark]
public DataRow[] SelectMethod() =>
_dataTable.Select("COLUMN_0 = 'DATA_10' OR COLUMN_1 like 'DATA_1%'", "COLUMN_0");
[Benchmark]
public DataRow[] CreateIndexAndSelectMethod()
{
_dataTable.DefaultView.Sort = "COLUMN_0, COLUMN_1";
return _dataTable.Select("COLUMN_0 = 'DATA_10' OR COLUMN_1 like 'DATA_1%'", "COLUMN_0");
}
[Benchmark]
public DataRow[] SelectMethodWithIndex() =>
_dataTable.Select("COLUMN_0 = 'DATA_10' OR COLUMN_1 like 'DATA_1%'", "COLUMN_0");
[Benchmark]
public DataRow[] LinqToDataSet() =>
_dataTable.AsEnumerable()
.Where(x => x.Field<string>("COLUMN_0") == "DATA_10" && x.Field<string>("COLUMN_1").StartsWith("DATA_1"))
.ToArray();
}
前に .NET 6 と .NET Framework で比較したときには .NET 6 の方が .NET Framework よりも遅いケースが多々あったのでびっくりしたのですが .NET 8 と .NET 9 はどうなるでしょうか。実際に計測した結果は以下のようになりました。Ratio 列が .NET Framework 4.8 に対する比率です。
Method | Job | Runtime | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|---|---|---|---|---|---|
SelectMethod | .NET 6.0 | .NET 6.0 | 357.53 ms | 9.575 ms | 26.848 ms | 352.42 ms | 1.21 | 0.13 | 1000.0000 | - | 10773056 B | 0.77 |
SelectMethod | .NET 8.0 | .NET 8.0 | 245.57 ms | 4.580 ms | 11.821 ms | 244.22 ms | 0.83 | 0.08 | 1000.0000 | - | 10772912 B | 0.77 |
SelectMethod | .NET 9.0 | .NET 9.0 | 233.71 ms | 5.184 ms | 14.706 ms | 228.72 ms | 0.79 | 0.08 | 1000.0000 | - | 10772576 B | 0.77 |
SelectMethod | .NET Framework 4.8 | .NET Framework 4.8 | 298.69 ms | 9.098 ms | 26.103 ms | 290.07 ms | 1.01 | 0.12 | 2000.0000 | 1000.0000 | 13981776 B | 1.00 |
CreateIndexAndSelectMethod | .NET 6.0 | .NET 6.0 | 435.11 ms | 8.338 ms | 15.660 ms | 432.58 ms | 1.13 | 0.05 | 2000.0000 | 1000.0000 | 24900008 B | 0.89 |
CreateIndexAndSelectMethod | .NET 8.0 | .NET 8.0 | 453.61 ms | 48.833 ms | 143.986 ms | 373.70 ms | 1.18 | 0.37 | 2000.0000 | 1000.0000 | 24899864 B | 0.89 |
CreateIndexAndSelectMethod | .NET 9.0 | .NET 9.0 | 368.98 ms | 16.185 ms | 45.913 ms | 349.32 ms | 0.96 | 0.12 | 2000.0000 | 1000.0000 | 24899528 B | 0.89 |
CreateIndexAndSelectMethod | .NET Framework 4.8 | .NET Framework 4.8 | 384.22 ms | 7.676 ms | 10.761 ms | 380.84 ms | 1.00 | 0.04 | 3000.0000 | 1000.0000 | 28126216 B | 1.00 |
SelectMethodWithIndex | .NET 6.0 | .NET 6.0 | 219.20 ms | 4.967 ms | 13.763 ms | 214.99 ms | 1.43 | 0.19 | 1000.0000 | - | 7531008 B | 0.70 |
SelectMethodWithIndex | .NET 8.0 | .NET 8.0 | 153.90 ms | 5.477 ms | 15.085 ms | 149.97 ms | 1.00 | 0.15 | 1000.0000 | - | 7530864 B | 0.70 |
SelectMethodWithIndex | .NET 9.0 | .NET 9.0 | 137.29 ms | 2.605 ms | 3.000 ms | 137.37 ms | 0.90 | 0.10 | 1000.0000 | - | 7530528 B | 0.70 |
SelectMethodWithIndex | .NET Framework 4.8 | .NET Framework 4.8 | 155.43 ms | 7.345 ms | 20.474 ms | 147.93 ms | 1.01 | 0.18 | 1000.0000 | - | 10729504 B | 1.00 |
LinqToDataSet | .NET 6.0 | .NET 6.0 | 12.01 ms | 0.443 ms | 1.168 ms | 11.88 ms | 0.79 | 0.14 | - | - | 1192 B | NA |
LinqToDataSet | .NET 8.0 | .NET 8.0 | 11.20 ms | 0.452 ms | 1.325 ms | 11.08 ms | 0.74 | 0.14 | - | - | 1048 B | NA |
LinqToDataSet | .NET 9.0 | .NET 9.0 | 11.35 ms | 0.533 ms | 1.530 ms | 11.17 ms | 0.75 | 0.15 | - | - | 704 B | NA |
LinqToDataSet | .NET Framework 4.8 | .NET Framework 4.8 | 15.63 ms | 0.988 ms | 2.771 ms | 14.78 ms | 1.03 | 0.24 | - | - | - | NA |
.NET 6 は、確かに .NET Framework に比べて遅いですが、.NET 8 と .NET 9 は .NET Framework に比べて速い結果になりました。.NET 9 は .NET 8 よりもさらに速い結果になりました。DataSet 自体には手が入ったとは考えにくいです。そのため .NET 7, 8, 9 の Performance Improvements の凄く長いブログの積み重ねがこの結果になっているのかもしれません。
- Performance Improvements in .NET 7
- Performance Improvements in .NET 8
- Performance Improvements in .NET 9
まとめ
.NET 8 と .NET 9 は .NET Framework に比べて DataSet の性能が向上していることがわかりました。.NET 9 は .NET 8 よりもさらに速い結果になりました。個人的には .NET 8 と .NET 9 でも .NET 6 と同じような結果になるかと思っていたので、意外でした。
ということでパフォーマンスの計測はしましたが .NET では基本的に DataSet
や DataTable
や、それに関連する TableAdapter
などは非同期メソッドもないですし 20 年以上前に設計されたレガシーテクノロジーなので使わないようにしましょう。
Discussion