DataGridViewの列追加が遅い → 列を非表示にして追加すればいいよ

普通DataGridViewを使う状況って、DBから取得したデータの表示/編集とかに使うと思うんだけど、
それはつまり行方向のデータ、つまりレコードが大量にある状況での使い方ですね。
大抵の場合、行>列です。
行数は1000とか10000とか扱っても、列は多くても精々20列とかその程度じゃないでしょうか。

なので、大量の行の追加(Rows.Add/AddRange)や仮想モードにおける行数の設定(RowCount)を使ってるときは気づかなかったのですが、

仕事でテーブルのデータを列方向に表示したいって要望があって、しかも仮想モードを使用する前提があって、
とりあえず行の場合と同じように、列を1000個設定してみたんですが

恐ろしいほど時間が掛かったのですよ。

まず状況の前提として、仮想モード(DataGridView.VirtualMode = true)です。
バインドモードでは試してないですが、たぶん仮想モードじゃなくても同じです。
表示するデータは別スレッドで随時取得し、CellValueNeededイベントで表示に必要なデータを渡します。

通常、仮想モードでは扱えるレコード数をあらかじめ設定しておきます。
DataGridView.RowCount=1000;
って感じです。
この処理は一瞬で終わります。
この時恐らく内部的には実際に1000行分のセルを用意するわけではないのでしょう。でないと仮想モードの意味がありません。

で、列でも同様に扱えると思って
DataGridView.ColumnCount = 1000;
としてみたんですよ。そしたら例外が発生しました。

System.InvalidOperationException: 列の FillWeight 値の合計が 65535 を超えることはできません。

FillWeight列(DataGridViewColumn)のプロパティで、デフォルトで100が設定され、655列を超えると上記例外が発生する模様。
コレに対処するには、ColumnAddedイベントでFillWeight プロパティに小さな値を設定する

private void dataGridView1_ColumnAdded(object sender, DataGridViewColumnEventArgs e)
{
e.Column.FillWeight = 0.1f;
}

か、DataGridViewColumnインスタンスのFilWeightに小さな値をセットしてからAddするか

for (int i = 0; i < 1000; i++) { var column = new DataGridViewTextBoxColumn(); column.FillWeight = 0.1f; dataGridView1.Columns.Add(column); }

で、例外の発生は止められます。

気を取り直して、ColumnCount=1000をしてみました。

・・・・
数分後、ようやく表示されました。時間測ってないけど、5分以上掛かったと思います。

やり方を変えて、DataGridView.Columns.Addを1000回ループで回してみました。
結果、やっぱり時間掛かりまくりでした。
DataGridViewColumnを一旦配列に詰めてからAddRangeしても同じでした。

色々試していてわかったことは

  • DataGridViewColumn.Visibleをfalseにした状態でのDataGridView.Columns.Addは早い。
  • AddしたDataGridViewColumnをVisible=trueにするのも早い。
  • DataGridViewColumn.Visible=trueな列が多いほど、それ以降のAddはVisibleの状態に関わらず遅くなる
  • 描画処理はこの遅さの原因ではなさそう。無関係

たとえばVisible=falseの列を100列追加してから全部Visible=trueに戻して、そのあとVisible=falseな列を追加すると、
Visible=trueな列を100列追加したあとに列追加するのと同じ時間が掛かりました。

この現象をさらに調べるために、CLR Profilerを使ってみたところ、
Visible=falseなDataGridViewTextBoxColumnを100列追加後にVisible=trueにした場合
Allocated bytesの値が900kbyte程度だったのに対して、

Visible=trueなDataGridViewTextBoxColumnを100列追加した場合
Allocated bytesの値が5Mbyte近くでした。
Visible=trueなDataGridViewTextBoxColumnを200列追加した場合だと
17Mを超えました。

内部でなにをそんなにアロケートしてるかわかりませんが、メモリアロケート(New)って比較的重い処理なので、
それをこれだけの差が出るほどやってれば、そりゃ遅くもなるよ。

またVisible=falseなほうはGCが走っていないのに対し、Visible=trueのほうは3回GCが走ってました。
GCもかなり重い処理です。

つまり、大量の列を追加する場合は
1.一旦Visible=falseにしてからAddする。
2.すべての列のAddが終わったらVisible=trueに戻す。
が良いということです。

こんな感じに。

List<datagridviewcolumn> list = new List<datagridviewcolumn>();
for (int i = 0; i < 1000; i++) { var column = new DataGridViewTextBoxColumn(); column.FillWeight = 0.1f; column.Visible = false; list.Add(column); } dataGridView1.Columns.AddRange(list.ToArray()); foreach (var column in list) { column.Visible = true; }

なのですがね・・・。仕事の要望を実現するには、随時列数を増やしつつ、表示もできなきゃいけないんですよね・・・。
でも随時Columns.AddでVisible=trueしてたら遅くなるわけで。
どうしたものやら・・・

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

認証のために問題を解いて下さい * Time limit is exhausted. Please reload CAPTCHA.