🐕

DataTable からデータ抽出方法の性能比較 on .NET 6, 8 and 9

2024/11/08に公開

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 の凄く長いブログの積み重ねがこの結果になっているのかもしれません。

まとめ

.NET 8 と .NET 9 は .NET Framework に比べて DataSet の性能が向上していることがわかりました。.NET 9 は .NET 8 よりもさらに速い結果になりました。個人的には .NET 8 と .NET 9 でも .NET 6 と同じような結果になるかと思っていたので、意外でした。

ということでパフォーマンスの計測はしましたが .NET では基本的に DataSetDataTable や、それに関連する TableAdapter などは非同期メソッドもないですし 20 年以上前に設計されたレガシーテクノロジーなので使わないようにしましょう。

Microsoft (有志)

Discussion