V4 V5

104 万行 × 16,384 列のシートを作っても、値を書くまでメモリはほとんど使いません。 V5 は実在するセルにだけコストを払う設計になっており、シートの寸法ではなく 「実際に何セル書いたか」「画面に何セル映っているか」で速度が決まります。

このページは、その特性を前提にコードを書くための指針です。

前提にしてよいこと

#特性
1空セルはコストを持たない。 フル寸法のシートを 100 枚作っても、空ならメモリはほぼゼロ
2セルあたり約 16〜23 バイト。 値は 16 バイトの構造体で、boxing は発生しない
3同じ内容の書式は 1 つに統合される。 何万セルに同じ書式を当てても実体は 1 つ
4同じ文字列は 1 つに統合される。 繰り返し現れる文字列はシート内で共有される
5行・列の設定は差分だけを持つ。 高さや幅を変えていない行はエントリを持たない
6走査は実在するセルの数に比例する。 空セルは訪問されない
7描画と操作は可視範囲に比例する。 シートの寸法には依存しない

これらは仕様として保証します。前提にしてコードを書いて構いません。

走査は使用範囲に絞る

一番多い性能問題は、0 から RowCount までを素朴に回してしまうことです。 空セルの読み出し自体は安価ですが、104 万回という回数が現実的ではありません。

using unvell.ReoGrid.Core;
using unvell.ReoGrid.Core.Style;

// 良い例 — 使用範囲にクランプしてから回す
if (ws.TryGetUsedRange(out var used))
{
	for (int r = used.Row; r <= used.EndRow; r++)
		for (int c = used.Col; c <= used.EndCol; c++)
			_ = ws.GetValue(r, c);
}

TryGetUsedRange は値・数式・書式のどれかが入っている範囲を返します。 シートが空なら false です。

疎なデータは ReadWindow で走査する

使用範囲は広いが中身が飛び飛び、という形(1 万行おきに 1 行だけ埋まっているなど)では、 ReadWindow が明確に速くなります。実在するセルだけがコールバックに渡り、 空セルでは呼ばれません。

// さらに良い例 — 実在するセルだけが訪問される(空セルは呼ばれない)
ws.ReadWindow(0, 0, ws.RowCount, ws.ColumnCount, (row, col, value, styleId) =>
{
	_ = value;
});

描画・XLSX 出力・PDF 出力はいずれもこの形を使っています。集計やエクスポートを 自分で書く場合も、まずこれを検討してください。

行・列全体の書式は行・列に設定する

// 良い例 — 列の既定スタイルは 1 エントリで済む
ws.SetColumnStyle(2, new StyleRecord { TextAlign = HAlign.Right });

同じことをセルのループで書くと、その列のセル数ぶんのエントリが実体化します。 特性 1(空セルはコストを持たない)が失われる、最も多い原因です。

見た目は同じでも、メモリ使用量と処理時間はまったく違います。詳しくは スタイルの継承 を参照してください。

書式は使い回さなくてよい

同じ内容の書式は自動的に 1 つに統合されるため、StyleRecord を変数に取っておく 必要はありません。ループの中で毎回 new しても実体は共有されます。

// 同じ内容のスタイルは 1 つに統合される(record の値等価性でインターン)
for (int r = 0; r < 10_000; r++)
	ws.SetCellStyle(r, 0, new StyleRecord { Bold = true });

int distinct = ws.DistinctStyleCount;   // 増えるのは 1 つだけ

DistinctStyleCount は診断に使えます。想定より大きい場合は、セルごとに微妙に違う 書式(1 セルだけフォントサイズが 0.5 違う、など)が生まれていないか確認してください。

大量のデータを流し込む

数万行を書き込むときは、次の順序が最も速くなります。

  1. 行・列のサイズと書式を先に決める(ws.Rows.SetSize / ws.Columns.SetSize / SetRowStyle / SetColumnStyle
  2. 値を書く(SetNumber / SetText が最も軽い。SetObjectValue は型判定のぶん遅い)
  3. 数式を書く

SetFormulaそのセルと、それに依存するセルをその場で再計算します。数式どうしが 依存し合う表を流し込むときは、依存される側(合計の元になるセル)から先に書くと 再計算の回数が減ります。順序を気にしたくない場合は、値だけ先に全部書いてから数式を書き、 最後に ws.Recalculate() を 1 回呼んでください。

RowDataSource を設定すると、行を表示されたときに初めて用意することもできます。 数百万行のデータベース結果をシートに見せる場合に使います。

ws.RowDataSource = new MyRowSource();   // IRowDataSource を実装する
ws.EnsureRowsLoaded(0, 99);             // 明示的に先読みしたいとき

コントロールは表示直前に可視範囲ぶんの EnsureRowsLoaded を自動的に呼びます。 既に用意済みの行は読み飛ばされるため、毎フレーム呼ばれても問題ありません。

巨大な XLSX を開く場合は、ファイル側も部分読み込みにできます (XLSX 入出力OpenVirtual)。

特性を壊す書き方

やってはいけないこと何が起きるか代わりに
for (int r = 0; r < ws.RowCount; r++)104 万回の反復TryGetUsedRange でクランプ / ReadWindow
列全体にセル単位で書式を設定列のセル数ぶん実体化SetColumnStyle
空セルを「初期化」するために値を書く特性 1 を失う書かない。空のまま扱う
セルごとに少しずつ違う書式を作る統合が効かない書式のバリエーションを絞る
ループの中で Recalculate()毎回シート全体を再計算書き終えてから 1 回だけ

計測する

メモリと割り当ての実測にはベンチマークプロジェクトが使えます。

dotnet run -c Release --project ReoGrid.Core.Bench

アプリ側で当たりを付けるなら、ws.DistinctStyleCountws.TryGetUsedRange の 返す範囲を見るのが手軽です。想定より大きければ、上の表のどれかを踏んでいます。

次に読む

ページの内容は役に立ちましたか?