DioDocs for ExcelとClosedXMLのパフォーマンスを確認してみる

ClosedXMLとは?

ClosedXMLは、Excelファイルを操作するためのオープンソースライブラリ(OSS)です。Open XML SDKを直接使用するよりもシンプルで読みやすいコードが書けるように実装されています。

Excel業務のシステム化や自動化といった開発要件において、DioDocs for Excel(ディオドック)をご検討いただく際にも比較の対象としてよく挙げられるライブラリになっています。今回は、Excelファイルの読み込み/保存/計算といった基本的な処理にかかる時間とメモリ使用量をBenchmarkDotnetを使用して測定してみます。

実行環境

ベンチマークの測定には、.NET 10で動作するコンソールアプリケーションを作成して使用しています。測定に使用した実行環境は以下のとおりです。

実行する内容

このコンソールアプリケーションを実行して測定する処理は以下となっています。

  1. 10,000行×30列の各セルに数値を設定/取得/Excelファイルに保存
  2. 10,000行×30列の各セルに文字列を設定/取得/Excelファイルに保存
  3. 10,000行×30列の各セルに日付を設定/取得/Excelファイルに保存
  4. 大きなサイズのExcelファイルを読み込み/計算/Excelファイルに保存
  5. 10,000行×2列の各セルに数値を入力、それらを参照する10,000行×30列の各セルに数式を設定/計算/Excelファイルに保存

1~3の処理では、数値/文字列/日付の値を10,000行×30 列(300,000セル)において、値を設定/値を取得/Excelファイルに保存、といった処理のパフォーマンスを測定します。

以下は数値を設定/取得/Excelファイルに保存する処理のパフォーマンスを測定するコードです。文字列の場合は”ABCDEFGHIJKLMNOPQRSTUVWXYZ”を使用します。 また、日付の場合はDateTimeオブジェクトを使用します。

10,000行×30列の各セルに数値を設定する

DioDocs fro Excel

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestSetRangeValues_Double()
{
    IWorkbook workbook = new Workbook();
    IWorksheet worksheet = workbook.Worksheets[0];
    worksheet.Name = "diodocs";

    double[,] values = new double[RowCount, ColumnCount];

    for (int i = 0; i < RowCount; i++)
    {
        for (int j = 0; j < ColumnCount; j++)
        {
            values[i, j] = i + j;
        }
    }

    worksheet.Range[0, 0, RowCount, ColumnCount].Value = values;
}

ClosedXML

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestSetRangeValues_Double()
{
    using var workbook = new XLWorkbook();

    var worksheet = workbook.Worksheets.Add("closedxml");

    for (int r = 0; r < RowCount; r++)
    {
        for (int c = 0; c < ColumnCount; c++)
        {
            worksheet.Cell(r + 1, c + 1).Value = (double)(r + c);
        }
    }
}

10,000行×30列の各セル設定した値(数値)を取得する

DioDocs for Excel

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestGetRangeValues_Double()
{
    IWorkbook workbook = new Workbook();
    IWorksheet worksheet = workbook.Worksheets[0];
    worksheet.Name = "diodocs";

    double[,] values = new double[RowCount, ColumnCount];

    for (int i = 0; i < RowCount; i++)
    {
        for (int j = 0; j < ColumnCount; j++)
        {
            values[i, j] = i + j;
        }
    }

    worksheet.Range[0, 0, RowCount, ColumnCount].Value = values;

    object tmpValues = worksheet.Range[0, 0, RowCount, ColumnCount].Value;
}

ClosedXML

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestGetRangeValues_Double()
{
    using var workbook = new XLWorkbook();

    var worksheet = workbook.Worksheets.Add("closedxml");

    for (int r = 0; r < RowCount; r++)
    {
        for (int c = 0; c < ColumnCount; c++)
        {
            worksheet.Cell(r + 1, c + 1).Value = (double)(r + c);
        }
    }

    for (int r = 0; r < RowCount; r++)
    {
        for (int c = 0; c < ColumnCount; c++)
        {
            double d = worksheet.Cell(r + 1, c + 1).GetValue<double>();
        }
    }
}

10,000行×30列の各セルに数値を設定してExcelファイルに保存する

DioDocs for Excel

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestSaveRangeValues_Double()
{
    IWorkbook workbook = new Workbook();
    IWorksheet worksheet = workbook.Worksheets[0];
    worksheet.Name = "diodocs";

    double[,] values = new double[RowCount, ColumnCount];

    for (int i = 0; i < RowCount; i++)
    {
        for (int j = 0; j < ColumnCount; j++)
        {
            values[i, j] = i + j;
        }
    }

    worksheet.Range[0, 0, RowCount, ColumnCount].Value = values;

    workbook.Save("./files/gcexcel-saved-doubles.xlsx");
}

ClosedXML

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestSaveRangeValues_Double()
{
    using var workbook = new XLWorkbook();

    var worksheet = workbook.Worksheets.Add("closedxml");

    for (int r = 0; r < RowCount; r++)
    {
        for (int c = 0; c < ColumnCount; c++)
        {
            worksheet.Cell(r + 1, c + 1).Value = (double)(r + c);
        }
    }

    workbook.SaveAs("./files/closedxml-saved-doubles.xlsx");
}

4の処理では、大きなサイズのExcelファイルを読み込み/計算/Excelファイルに保存、といった処理のパフォーマンスを測定します。このファイルにはワークシートが計算されるたびに再計算が必要な揮発性関数(セルが計算されるたびに値が変化する関数。NOWやRAND、TODAYなど)を含む多数の数式が設定されています。 

大きなサイズのExcelファイルを読み込み/計算/Excelファイルに保存

DioDocs for Excel

[Benchmark]
public void TestOpenBigExcelFile()
{
    IWorkbook workbook = new Workbook();
    workbook.Open("./files/test-performance.xlsx");
}

[Benchmark]
public void TestCalcBigExcelFile()
{
    IWorkbook workbook = new Workbook();
    workbook.Open("./files/test-performance.xlsx");

    workbook.Dirty();
    workbook.Calculate();    
}

[Benchmark]
public void TestSaveBigExcelFile()
{
    IWorkbook workbook = new Workbook();
    workbook.Open("./files/test-performance.xlsx");

    workbook.Save("./files/gcexcel-saved-output.xlsx");
}

ClosedXML

[Benchmark]
public void TestOpenBigExcelFile()
{
    using var workbook = new XLWorkbook("./files/test-performance.xlsx");
}

[Benchmark]
public void TestCalcBigExcelFile()
{
    using var workbook = new XLWorkbook("./files/test-performance.xlsx");

    workbook.RecalculateAllFormulas();
}

[Benchmark]
public void TestSaveBigExcelFile()
{
    using var workbook = new XLWorkbook("./files/test-performance.xlsx");

    workbook.SaveAs("./files/closedxml-saved-output.xlsx");
}

5の処理では、10,000行×2列の数値を設定してそれらを参照する10,000行×30列の各セルに 
おいて、数式を設定/計算/Excelファイルに保存数式を設定、といった処理のパフォーマンスを測定します。

10,000行×30列の各セルに数式を設定する

DioDocs for Excel

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestSetRangeFormulas()
{
    IWorkbook workbook = new Workbook();
    workbook.ReferenceStyle = ReferenceStyle.R1C1;
    IWorksheet worksheet = workbook.Worksheets[0];
    worksheet.Name = "diodocs";

    double[,] values = new double[RowCount, 2];

    for (int i = 0; i < RowCount; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            values[i, j] = i + j;
        }
    }

    worksheet.Range[0, 0, RowCount, 2].Value = values;

    worksheet.Range[0, 2, RowCount, ColumnCount].Formula = "=SUM(RC[-2],RC[-1])";
}

ClosedXML

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestSetRangeFormulas()
{
    using var workbook = new XLWorkbook();

    var worksheet = workbook.Worksheets.Add("closedxml");

    for (int r = 1; r <= RowCount; r++)
    {
        worksheet.Cell(r, 1).Value = r - 1;
        worksheet.Cell(r, 2).Value = r;
    }

    for (int r = 1; r <= RowCount; r++)
    {
        for (int c = 3; c <= ColumnCount + 2; c++)
        {
            var cell = worksheet.Cell(r, c);
            var ref1 = worksheet.Cell(r, c - 2).Address.ToString();
            var ref2 = worksheet.Cell(r, c - 1).Address.ToString();
            cell.FormulaA1 = string.Format("SUM({0},{1})", ref1, ref2);
        }
    }
}

10,000行×30列の各セルに数式を設定、計算する

DioDocs for Excel

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestCalcRangeFormulas()
{
    IWorkbook workbook = new Workbook();
    workbook.ReferenceStyle = ReferenceStyle.R1C1;
    IWorksheet worksheet = workbook.Worksheets[0];
    worksheet.Name = "diodocs";

    double[,] values = new double[RowCount, 2];

    for (int i = 0; i < RowCount; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            values[i, j] = i + j;
        }
    }

    worksheet.Range[0, 0, RowCount, 2].Value = values;
    worksheet.Range[0, 2, RowCount, ColumnCount].Formula = "=SUM(RC[-2],RC[-1])";

    workbook.Dirty();
    workbook.Calculate();    
}

ClosedXML

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestCalcRangeFormulas()
{
    using var workbook = new XLWorkbook();

    var worksheet = workbook.Worksheets.Add("closedxml");

    for (int r = 1; r <= RowCount; r++)
    {
        worksheet.Cell(r, 1).Value = r - 1;
        worksheet.Cell(r, 2).Value = r;
    }

    for (int r = 1; r <= RowCount; r++)
    {
        for (int c = 3; c <= ColumnCount + 2; c++)
        {
            var cell = worksheet.Cell(r, c);
            var ref1 = worksheet.Cell(r, c - 2).Address.ToString();
            var ref2 = worksheet.Cell(r, c - 1).Address.ToString();
            cell.FormulaA1 = string.Format("SUM({0},{1})", ref1, ref2);
        }
    }

    workbook.RecalculateAllFormulas();
}

10,000行×30列の各セルに数式を設定、保存する

DioDocs for Excel

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestSaveRangeFormulas()
{
    IWorkbook workbook = new Workbook();
    workbook.ReferenceStyle = ReferenceStyle.R1C1;
    IWorksheet worksheet = workbook.Worksheets[0];
    worksheet.Name = "diodocs";

    double[,] values = new double[RowCount, 2];

    for (int i = 0; i < RowCount; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            values[i, j] = i + j;
        }
    }

    worksheet.Range[0, 0, RowCount, 2].Value = values;
    worksheet.Range[0, 2, RowCount, ColumnCount].Formula = "=SUM(RC[-2],RC[-1])";

    workbook.ReferenceStyle = ReferenceStyle.A1;
    workbook.Save("./files/gcexcel-saved-formulas.xlsx");
}

ClosedXML

private const int RowCount = 10000;
private const int ColumnCount = 30;

[Benchmark]
public void TestSaveRangeFormulas()
{
    using var workbook = new XLWorkbook();

    var worksheet = workbook.Worksheets.Add("closedxml");

    for (int r = 1; r <= RowCount; r++)
    {
        worksheet.Cell(r, 1).Value = r - 1;
        worksheet.Cell(r, 2).Value = r;
    }

    for (int r = 1; r <= RowCount; r++)
    {
        for (int c = 3; c <= ColumnCount + 2; c++)
        {
            var cell = worksheet.Cell(r, c);
            var ref1 = worksheet.Cell(r, c - 2).Address.ToString();
            var ref2 = worksheet.Cell(r, c - 1).Address.ToString();
            cell.FormulaA1 = string.Format("SUM({0},{1})", ref1, ref2);
        }
    }

    workbook.SaveAs("./files/closedxml-saved-formulas.xlsx");
}

測定結果

DioDocs for ExcelとClosedXMLで、1~5の処理を測定した結果です。

DioDocs for Excel

DioDocs for Excelの結果

ClosedXML

NPOIの結果

「Mean(Arithmetic mean of all measurements)」の数値を比較してみると、ClosedXMLよりもDioDocs for Excelの処理時間が短いことが確認できます。また、「Allocated」の数値を比較してみるとClosedXMLよりもDioDocs for Excelで測定した結果の方がメモリ使用量も少ないことも確認できます。

今回のような大量のデータと数式を設定/取得/保存したり、大量のデータと数式が含まれているExcelファイルの読み込み/保存/計算処理が必要になる要件においては、処理パフォーマンスが優れているDioDocs for Excelが非常に有効な選択肢になるかとおもいます。

上記で紹介した動作を確認できるサンプルはこちらです。

さいごに

弊社Webサイトでは、製品の機能を気軽に試せるデモアプリケーションやトライアル版も公開していますので、こちらもご確認いただければと思います。

また、ご導入前の製品に関するご相談やご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /