◆IDataReaderを実装しよう!

Top Page
└◆DestroyDataTable();
    ├─▶はじめに
    ├─▶実験
    ├─▶実装例
    └─▶おわりに


◆はじめに

こんにちは。
先日BulkCopyクラスを使うにあたって、
 ・DataTableを使うべきか?
 ・IDataReaderを実装したクラスを用意するべきか?
ということで学びがありました。

そこで、
「DataTable を使わずに IDataReader を使うことをお勧めします。」
「DataTable だとメモリ使うわパフォーマンス悪いわでいい事ないです。」
ということが実感できるサンプルを作ったので見ていってください。

《仕様》
    Step0: 何人かの「かわい」と何杯かの「ビール」があるものとする
    Step1: すべての「かわい」とすべての「ビール」を組み合わせた「かわいとビール」データを作成する
    Step2: 作成した「かわいとビール」の大量データをBulkCopy.WriteToServer()でDBにつっこむ

今回は元の2つのリストからBulkCopyするための大量データを作成するにあたって、
「DataTableを使う場合」と「IDataReaderを実装したクラスを使う場合」を比較し、
いかに「DataTableがDestroyするに値するものであるか」ということを実感していただきます。

◆イメージ図:2人の「かわい」と3杯の「ビール」の例
※「かわい」と「ビール」のリストは、Step0にてあらかじめ用意されているものとします。

イメージ図

▲GoToHeader();

◆実験

以下のようなサンプルコードで実験です。
    ※とりあえず「かわい100人」と「ビール100杯」で実験
    ※3つの自作クラスを使用 ⇒ 各自作クラスの中身は後述
        ①DataRepositoryクラス
            …Step0において「かわい」と「ビール」のリストを生成するクラス
        ②KawaiAndBeerDataReaderクラス
            …DBにBulkCopyする大量データを用意するIDataReaderを実装したクラス
        ③KawaiAndBeerDataTableクラス
            …DBにBulkCopyする大量データをDataTableとして用意するクラス

        static void Main(string[] args)
        {
            var connectionString = "Data Source = *;Initial Catalog = *;User ID = *;Password = *";
            var sw1 = new Stopwatch();
            var sw2 = new Stopwatch();
            //★Step0: あらかじめ「100人のかわい」と「100杯のビール」は用意されているとする
            var kawais = DataRepository.GetKawais(100);
            var beers = DataRepository.GetBeers(100);
            //★Step1: 100*100 = 10000件の大量データを作成する
            sw1 = Stopwatch.StartNew();
            var reader = new KawaiAndBeerDataReader(kawais, beers);//←IDataReaderの場合
                //var table = new KawaiAndBeerDataTable(kawais, beers);//←DataTableの場合
            sw1.Stop();
            //★Step2: 大量データをBulkCopy.WriteToServer()
            sw2 = Stopwatch.StartNew();
            using (var bc = new SqlBulkCopy(connectionString)
            {
                DestinationTableName = "BulkCopyTestTable",
            })
            {
                bc.WriteToServer(reader);//←IDataReaderの場合
                    //bc.WriteToServer(table);//←DataTableの場合
            }
            sw2.Stop();
            //結果出力
            Console.WriteLine("DataReader:");//←IDataReaderの場合
                //Console.WriteLine("DataTable:");//←DataTableの場合
            Console.WriteLine("Step1: " + sw1.Elapsed);
            Console.WriteLine("Step2: " + sw2.Elapsed);
            Console.ReadLine();
        }

《出力結果》
    DataTable:
    Step1: 00:00:00.0499923
    Step2: 00:00:00.2247327
    DataReader:
    Step1: 00:00:00.0006408
    Step2: 00:00:00.2387426

  Σ(゚д゚;)


Step2はあまり差が見られませんが、Step1において

実行速度差、実に78倍...‼

以下の条件のもと、データをもう少し集めてみました。
  ・「かわい」を100人で固定し、「ビール」の杯数を変えてデータをとる
  ・同じ条件のもと10回実行し、最高記録と最低記録を除いた平均をとる

◆実行時間
実験結果
どうですかみなさん。
DataTableをDestroyしたくなってきましたか?
(まさかこれほどまでに明暗が分かれるとは思ってもみませんでした...)

どうやらDataTableだろうがIDataReaderだろうが、
WriteToServerのパフォーマンスにはそれほど影響がない
ということもわかりましたね!

なぜこのようなパフォーマンスの差が生まれるのか?
実装方法を見てみましょう!

▲GoToHeader();

◆実装例

以下、サンプルコードです:

  ①元となるデータを作成するDataRepositoryクラス(クリックで展開)

  ②DataTableクラス(クリックで展開)

  ③IDataReaderを実装したクラス(クリックで展開)

《学び》
  DataTableは、
    ・既存のデータを複製して大量データをまるっと作成する...
      ⇒故に遅く、コストも高い...
  IDataReaderを実装したクラスは、
    ・既存データの参照の受け渡しをうまいことやってくれる!
      ⇒大量データを生成せずに大量データのInsertを可能にする!
      ⇒故に早く、コストも低い!

▲GoToHeader();

◆おわりに

「パフォーマンスってまぁ気にしなきゃいけないと思うけど、実際どれほどの差がでるわけ?」
と思ってPGしてきましたが、今後は生まれ変わった気持ちでソースと向き合うことができそうです。

実装ができて半人前、パフォーマンスや可読性・保守性の高いコードが書けて一人前!
今後も皆様からのツッコミ、お待ちしてます!

▲GoToHeader();
inserted by FC2 system