改善されたSPREADのパフォーマンスとメモリ消費を検証する

前回の記事では「SPREAD for Windows Forms(スプレッド)」のバージョン「12.0J」で追加された「ExcelのVBAと互換性のあるAPIセット」を使用するメリットについてご紹介しました。

上記のVBA互換のAPIを使用することで、VBAと同様のコードで各種操作が実行できたり、コードがシンプルになるというメリットがありましたが、その他にパフォーマンスの向上も期待できます。
本記事では、Excelファイルのインポート、フィルタ処理、スタイルの設定におけるパフォーマンスとメモリ消費のバージョン間の変化について検証します。

パフォーマンスとメモリ消費のバージョン間の比較

少し遡って、SPREADのバージョン「11.0J」では、メモリの使用効率が改善されたことによってパフォーマンスの改善も実現されました。

これによって下記のような操作での処理時間が短縮されています。

  • 大きなファイルの読み込み(本記事のExcelファイルのインポートで検証)
  • フィルタ処理(本記事のフィルタ処理で検証)
  • 多数の表示項目に対するスタイルの設定(本記事のスタイルの設定で検証)

そして、バージョン「12.0J」ではメモリ使用効率がさらに向上し、処理時間とメモリ消費がバージョン「11.0J」よりも改善されています。次の表は、バージョン「10.0J」、「11.0J」、および「12.0J」での各処理に要した時間と消費したメモリ容量を計測した結果です。
※ 処理前より、処理後の方がメモリ使用量が減っているケースはマイナス表記となっています。

操作 処理時間(ミリ秒) メモリ消費(KB)
ver.12 ver.11 ver.10 ver.12 ver.11 ver.10
Excelファイルの
インポート
20,797 27,543 46,952 398,476 462,708 521,204
フィルタリストの
表示
8,076 6,492 6,875 2,497 11,968 -12,540
フィルタの実行 14 27 9,516 -276 216 -47,168
スタイルの設定 従来のAPI 12,196 7,759 6,957 996 780 318,380
VBA互換のAPI 3,492 -48,668

「スタイルの設定」に関しては処理時間が少し増えていますが、ここでもVBA互換のAPIを使用することでパフォーマンスの改善を実現できます。
この表を棒グラフにすると次のようになります。

処理時間の比較
バージョンごとの各処理のパフォーマンス
※ 12.0Jのスタイルの設定にはVBA互換のAPIを使用
メモリ消費の比較
メモリ消費の激しいExcelインポートにおいてもバージョンを重ねるごとに改善

この計測結果については、以降の項目で各操作ごとに考察します。

計測の方法

上記の処理時間とメモリ消費は、次のようなシートを使って下記の方法で計測しています。

var sheet = spread.ActiveSheet;
sheet.ColumnCount = 50;
sheet.RowCount = 10000;
sheet.AutoFilterMode = AutoFilterMode.EnhancedContextMenu;
sheet.AutoSortEnhancedContextMenu = true;
sheet.Columns[0].AllowAutoFilter = true;
sheet.Columns[0].AllowAutoSort = true;
sheet.RowHeader.Columns[0].Width = 60;
for (var i = 0; i < sheet.RowCount; i++)
{
    sheet.SetValue(i, 0, $"A{i % 5000}");
    for (var j = 1; j < sheet.ColumnCount; j++)
    {
        sheet.SetValue(i, j, $"R{i}C{j}");
    }
}
// 処理開始時のメモリ使用量と時刻の取得
GC.Collect();
Memory1 = Environment.WorkingSet;
Time1 = DateTime.Now;

// ------------------------------
// ここで計測対象の処理を実行します
// ------------------------------

// 処理完了時のメモリ使用量と時刻の取得
Time2 = DateTime.Now;
GC.Collect();
Memory2 = Environment.WorkingSet;

// 計測結果の取得
処理時間 = Time2.Subtract(Time1).TotalMilliseconds;
メモリ消費 = Memory2 - Memory1;

Excelファイルのインポート

Excelファイルのインポート

Excelファイルを読み込む際にかかる時間とメモリ消費の増加量を調べるために、次のようなExcelファイルを使って検証しました。

  • シート数:40
  • 列数:50(標準セル:30+数値型セル:10+日付型セル:10)
  • 行数:5000
  • 交互行の背景色を行ごとに設定(条件付き書式は使用していない)

Excelファイルの読み込みには、FpSpreadクラスのOpenExcelメソッドを使います。

spread.OpenExcel("../../PerformanceTest_withStyle.xlsx", ExcelOpenFlags.TruncateEmptyRowsAndColumns);

Excelファイルのインポートでは、バージョン「10.0J」に比べてバージョン「12.0J」の処理時間は半分以下になり、メモリ消費も25%ほど削減されています。

フィルタ処理

フィルタ処理

フィルタ処理では、次の2つの操作について計測しました。この画像はフィルタリストの表示について計測したときのものです。また、SPREADのフィルタ機能には3種類ありますが、今回の計測では使用頻度の高いExcelライクな拡張フィルタリング機能を使いました。

  • フィルタリストの表示
  • フィルタの実行(手作業による操作)

フィルタリストの表示の計測は、SPREADのMouseDownイベントでフィルタのドロップダウンボタンが押下されたことを検出して計測を開始し、フィルタリストが表示されたときにFormのDeactivateイベントが発生するので、そのときに計測を終了します。

spread.MouseDown += (s, ea) =>
{
    var ht = spread.HitTest(ea.X, ea.Y);
    if (ht.Type == HitTestType.ColumnHeader && ht.HeaderInfo.Column == 0)
    {
        ・・・
        // 処理開始時のメモリ使用量と時刻の取得
        GetInitialInfo();
    }
};

private void Form1_Deactivate(object sender, EventArgs e)
{
    // 処理完了時のメモリ使用量と時刻の取得
    if (TestItem == "button2" && Time1 != DateTime.MinValue)
    {
        foreach (Control c in (sender as Form).Controls)
        {
            if (c is FpSpread)
            {
                textBox1.Text = GetResult(button2, c as FpSpread, true);
                ・・・
                break;
            }
        }
    }
}

もう1つのフィルタの実行については、AutoFilteringColumnイベントで計測を開始してAutoFilteredColumnイベントで計測を終了します。ここでは、1つのフィルタ項目だけを除外する(チェックを外す)操作を行ったときの処理時間を測りました。

spread.AutoFilteringColumn += (s, ea) =>
{
    ・・・
    // 処理開始時のメモリ使用量と時刻の取得
    GetInitialInfo();
};
spread.AutoFilteredColumn += (s, ea) =>
{
    // 処理完了時のメモリ使用量と時刻の取得
    textBox1.Text = GetResult(sender, spread, true);
    ・・・
};

フィルタリストの表示にかかる時間はバージョン間で違いはほとんどありませんが、フィルタの実行では、バージョン「10.0J」で10秒ほどかかっていた処理がバージョン「11.0J」と「12.0J」では瞬時に完了しています。

「12.0J」のVBA互換APIでさらにパフォーマンス改善

ここまで検証した処理では、SPREADの従来のAPIを使用した場合でもパフォーマンスの向上が確認できましたが、冒頭で少しご紹介したVBA互換のAPIを活用することでスタイル設定時のパフォーマンスも向上できます。

スタイルの設定

スタイルの設定

スタイルの設定については、50列×10000行のセルに背景色と文字色を設定したときの時間とメモリ消費を測りました。実際のアプリケーションのスタイルは、セル単位ではなく列単位または行単位で設定するのが一般的ですが、ここでは処理時間の違いが分かりやすいセル単位での設定を採用しています。

var sheet = spread.ActiveSheet;
for (var i = 0; i < sheet.RowCount; i++)
{
    for (var j = 0; j < sheet.ColumnCount; j++)
    {
        sheet.Cells[i, j].BackColor = Color.LavenderBlush;
        sheet.Cells[i, j].ForeColor = Color.Blue;
    }
}

従来のAPIを使った場合、処理時間については期待とは異なる結果になってしまいましたが、メモリ消費はバージョン「10.0J」に比べて「11.0J」と「12.0J」では99%以上削減されています。

このとき、以下のようにVBA互換APIを使用してスタイルを設定すると、12秒ほどだった処理時間が4秒未満に改善し、「10.0J」、「11.0J」と比較しても良い数値になります。

// この手法はLegacyBehaviorsのStyleフラグがオフのときにのみ有効です
var wsheet = spread.ActiveSheet.AsWorksheet();
var backColor = GrapeCity.Spreadsheet.Color.FromArgb(Color.LavenderBlush.ToArgb());
var foreColor = GrapeCity.Spreadsheet.Color.FromArgb(Color.Blue.ToArgb());
for (var i = 0; i < wsheet.RowCount; i++)
{
    for (var j = 0; j < wsheet.ColumnCount; j++)
    {
        wsheet.Cells[i, j].Interior.Color = backColor;
        wsheet.Cells[i, j].Font.Color = foreColor;
    }
}

描画制御によるパフォーマンスの改善

VBA互換APIを使用することで、スタイル設定の処理時間を改善することができましたが、それ以外にパフォーマンスを改善する方法として、描画処理の直前にSuspendLayoutメソッドを実行してコントロールの表示更新を一時停止させ、処理の後でResumeLayoutメソッドを実行してコントロールを再描画する方法があります。

この方法は、「11.0J」以前の従来のAPIでも使用できるので、今回の検証でもVBA互換APIを使ったスタイル設定ではなく、従来のセルのBackColorプロパティとForeColorプロパティを設定する方法を使っています。

// 描画の一時停止
spread.SuspendLayout();

// ------------------------------
// ここで計測対象の処理を実行します
// ------------------------------

// 描画の一時停止の解除
spread.ResumeLayout();

「12.0J」のVBA互換APIでは描画処理だけでなく、イベントの一時停止もできるようになりました。FpSpreadクラスのSuspendLayoutメソッドとResumeLayoutメソッドに加えて、GrapeCity.Spreadsheet.IWorkbookSetインタフェースのBeginUpdateメソッドとEndUpdateメソッドを使用することで、パフォーマンスをさらに向上できます。

// 描画の一時停止
spread.SuspendLayout();
// 描画とイベントの一時停止
spread.AsWorkbook().WorkbookSet.BeginUpdate();

// ------------------------------
// ここで計測対象の処理を実行します
// ------------------------------

// 描画とイベントの一時停止の解除
spread.AsWorkbook().WorkbookSet.EndUpdate();
// 描画の一時停止の解除
spread.ResumeLayout();

計測した結果を以下に示します。

操作 処理時間(ミリ秒)
描画とイベントの一時停止 描画の一時停止のみ 描画の制御なし
Excelファイルの
インポート
21,426 21,019 20,797
フィルタリストの
表示
6,486 6,502 6,517
フィルタの実行 15 14 14
スタイルの設定
(従来のAPI)
3,661 5,328 12,196

この表を棒グラフにすると次のようになります。

描画制御によるパフォーマンスの改善

これらのメソッドを使ったパフォーマンスの向上について弊社のナレッジベースでも紹介していますので、ご参考にしていただければと思います。

「描画速度(パフォーマンス)を向上させる方法」を見る
「数式を設定する際にパフォーマンスを上げる方法」を見る

今回検証に使用した各バージョンのプロジェクトは以下よりダウンロード可能です。

おわりに

SPREADの処理速度とメモリ使用の効率化がバージョンの更新のたびに行われていることをご理解いただけましたでしょうか。

そしてバージョン「12.0J」では、処理速度を改善する際にもVBA互換のAPIが威力を発揮しました。このVBA互換のAPIはバージョン「12.0J」以降で使用可能なので、ご利用を検討いただけますと幸いです。弊社Webサイトでは、SPREADの機能を手軽に体験できるデモアプリケーションを公開しています。Excelとの高い親和性のあるSPREADの豊富な機能を是非ご確認ください。