🐕

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

2021/11/28に公開約5,500字

12 年前に以下のような記事を書いたのですが、この記事が長らく自分のブログのページビューの 1 位に君臨していました。

https://blog.okazuki.jp/entry/20091217/1261056768

流石に、今はアクセス解析で上位に来ることは無く、月間ページビューも 500 に届かない程度になります。
ということで?同じ内容を .NET 6 でもやってみようと思います。ついでに .NET Framework 4.8 とも比べてみて .NET 6 が早い!ということも証明できたらいいなという感じで行こうと思います。

因みに、これを書いてる時点では計測していないので結果は爆死してる可能性があります。

計測に使ったコード

50000 行で 100 列のデータを適当につくって、検索をするパターンを DataTableSelect メソッドを使う方法(SelectMethod)と、インデックスを事前に作成した状態で Select を使う方法(SelectMethodWithIndex)と、インデックスを作成して Select をする方法(CreateIndexAndSelectMethod)と、LINQ to DataSet を使う方法(LinqToDataSet)で試してみました。

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System.Data;

namespace DataTableBenchmark;

[SimpleJob(runtimeMoniker: RuntimeMoniker.Net48, baseline: true)]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net60)]
[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();
}

GitHub にも一応あげています。

https://github.com/runceel/DataTableBenchmark

結果は以下のようになりました。

Method Job Runtime Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Allocated
SelectMethod .NET 6.0 .NET 6.0 338.79 ms 5.957 ms 9.620 ms 1.35 0.08 1000.0000 - 10,772,992 B
SelectMethod .NET Framework 4.8 .NET Framework 4.8 253.09 ms 4.873 ms 9.153 ms 1.00 0.00 2000.0000 1000.0000 13,981,776 B
CreateIndexAndSelectMethod .NET 6.0 .NET 6.0 483.37 ms 9.435 ms 14.689 ms 1.44 0.04 2000.0000 1000.0000 24,899,944 B
CreateIndexAndSelectMethod .NET Framework 4.8 .NET Framework 4.8 335.31 ms 4.906 ms 4.349 ms 1.00 0.00 3000.0000 1000.0000 28,126,216 B
SelectMethodWithIndex .NET 6.0 .NET 6.0 229.05 ms 4.537 ms 6.360 ms 2.29 0.06 - - 7,530,944 B
SelectMethodWithIndex .NET Framework 4.8 .NET Framework 4.8 99.49 ms 1.888 ms 2.174 ms 1.00 0.00 1000.0000 - 10,729,504 B
LinqToDataSet .NET 6.0 .NET 6.0 20.16 ms 0.394 ms 0.469 ms 0.93 0.02 - - 1,128 B
LinqToDataSet .NET Framework 4.8 .NET Framework 4.8 21.75 ms 0.419 ms 0.392 ms 1.00 0.00 - - -

結果は…、LINQ to DataSet の圧勝でした!!ただ、以外なのが .NET の方が LINQ to DataSet 以外の方法では早かったです。LINQ to DataSet もおそらく誤差の世界ですね…。メモリ アロケーションは .NET の方が少なめな感じですが、.NET の方が実行速度的には遅いとは…。意外でした。

DataSet や DataTable 自体が .NET の性能改善の恩恵を得られるような機能をつかって実装されていないとかなんですかね?
流石に今から DataSet や DataTable のコードを見る気にはならないですが個人的には意外でした。

雑なまとめ

とりあえず LINQ to DataSet かインデックスを作成した Select メソッドを使うのがいいことには変わりはないという結果になりました。
データの持ち方やリレーションによっては、また違う結果になるかもしれないので、自分たちのアプリで扱うスキーマを定義した DataSet や DataTable でいろいろ測ってみると面白いかもしれません。

Discussion

ログインするとコメントできます