かずきのBlog

C#やJavaやRubyとメモ書き

目次

Blog 利用状況

ニュース

わんくまBlogが不安定になったため、前に書いてたはてなダイアリーにメインを移動します。
かずきのBlog@Hatena
技術的なネタは、こちらにも、はてなへのリンクという形で掲載しますが、雑多ネタははてなダイアリーだけに掲載することが多いと思います。
コメント
プログラマ的自己紹介
お気に入りのツール/IDE
プロフィール
経歴
広告
アクセサリ

書庫

日記カテゴリ

[.NET][C#]当然っちゃ当然だけどDataTableとか使いようによっては遅い

今日は、ちょっくら調べ物でした。
そこで、気づいたのが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");
        }
    }
}

結果
image

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");
        }

    }
}

実行結果
image

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");
        }

    }
}

実行結果
image

ということで、Selectがダントツで遅いです。
いっぱつ限りならいいですが、ループ内でSelectを使って検索を繰り返すときはLinqかループでごりっとやっちゃいましょう。

 

ということを今日体感しました。

投稿日時 : 2009年6月25日 22:58

Feedback

# re: [.NET][C#]当然っちゃ当然だけどDataTableとか使いようによっては遅い 2009/06/26 11:06 aetos

> 列名でアクセスするときは倍くらい時間がかかってそうに見えます

列名から DataColumn を探すのにかかる時間が、もとから DataColumn を使う場合に上乗せされるからでしょうかね。

> 性能的にも生産性的にも型付DataSetが使えるシーンでは使っておくほうが無難だと思われます。

しかし何故か DataColumn を internal メンバにしてくれるので、アセンブリをまたいで使うような DataSet では列名を使わざるを得なかったり。

> ということで、Selectがダントツで遅いです。

DataView.Find との速度比較も欲しいなーと思ったり。
先日、仕事で書いてプログラムで、DataView を new するコストが馬鹿にならなかったことがありましたが。

# re: [.NET][C#]当然っちゃ当然だけどDataTableとか使いようによっては遅い 2009/06/26 11:09 aetos

ああ勘違い。
列プロパティを使えるところでは使えってことですね。

ところで、.NET 2.0 からは、Nullable 型になったりしてるんでしょうか?
でないと、NULL が入り得て、妥当な NullValue も設定できない列では、列名アクセスせざるを得ないので…

# [.NET][C#]当然っちゃ当然だけどDataTableとか使いようによっては遅い その2 2009/09/23 1:05 かずきのBlog

[.NET][C#]当然っちゃ当然だけどDataTableとか使いようによっては遅い その2

# [.NET][C#]DataTableからのデータ抽出方法の性能比較 2009/12/17 22:34 かずきのBlog

[.NET][C#]DataTableからのデータ抽出方法の性能比較

# re: [.NET][C#]当然っちゃ当然だけどDataTableとか使いようによっては遅い 2009/12/17 23:14 ひらぽん

ADO.NET 専修講座(VB.NET編) という本にこの辺の話が載ってましたので、6月頃試してみたことがあります。
で、実験結果↓

http://blogs.yahoo.co.jp/hilapon/2573935.html

DataColumn の事前バインディングが最も速かったです。

# re: [.NET][C#]当然っちゃ当然だけどDataTableとか使いようによっては遅い 2010/11/26 20:04 u0zzub8oboubr4897 zz zzihioglirs.bn

7894561230
789456130
7894561230
789945611230

タイトル
名前
Url
コメント