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

NPOIとは?

NPOIは、Javaで使われているExcelファイルなどのOfficeドキュメントファイルを操作するライブラリ「Apache POI」を .NETに移植したオープンソースライブラリ(OSS)です。

Excel業務をシステム化する要件において、DioDocs for Excel(ディオドック)をご検討いただく際にも比較の対象としてよく挙げられるライブラリになっています。過去にNPOIとDioDocs for Excelの機能を比較した資料を公開しましたが、その時点では処理パフォーマンスの比較にBenchmarkDotnetを使用していませんでした。

そこで今回は、Excelファイルの読み込み/保存/計算といった基本的な処理にかかる時間とメモリ使用量をBenchmarkDotnetを使用してあらためて測定してみます。

実行環境

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

実行する内容

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

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

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

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

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

DioDocs for Excel

private const int RowCount = 100000;
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;
}

NPOI

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

[Benchmark]
public void TestSetRangeValues_Double()
{
    XSSFWorkbook workbook = new XSSFWorkbook();
    var worksheet = workbook.CreateSheet("npoi");

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.CreateRow(r);
        for (int c = 0; c < ColumnCount; c++)
        {
            row.CreateCell(c).SetCellValue(r + c);
        }
    }

    workbook.Close();
}

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

DioDocs for Excel

private const int RowCount = 100000;
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;
}

NPOI

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

[Benchmark]
public void TestGetRangeValues_Double()
{
    XSSFWorkbook workbook = new XSSFWorkbook();
    var worksheet = workbook.CreateSheet("npoi");

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.CreateRow(r);
        for (int c = 0; c < ColumnCount; c++)
        {
            row.CreateCell(c).SetCellValue(r + c);
        }
    }

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.GetRow(r);
        for (int c = 0; c < ColumnCount; c++)
        {
            double d = row.GetCell(c).NumericCellValue;
        }
    }

    workbook.Close();
}

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

DioDocs for Excel

private const int RowCount = 100000;
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");
}

NPOI

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

[Benchmark]
public void TestSaveRangeValues_Double()
{
    XSSFWorkbook workbook = new XSSFWorkbook();
    var worksheet = workbook.CreateSheet("npoi");

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.CreateRow(r);
        for (int c = 0; c < ColumnCount; c++)
        {
            row.CreateCell(c).SetCellValue(r + c);
        }
    }

    using (FileStream fileOut = new FileStream("./files/poi-saved-doubles.xlsx", FileMode.Create, FileAccess.Write))
    {
        workbook.Write(fileOut);
    }

    workbook.Close();
}

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");
}

NPOI

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

    workbook.Close();
}

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

    workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll();

    workbook.Close();
}

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

    using (FileStream fileOut = new FileStream("./files/poi-saved-output.xlsx", FileMode.Create, FileAccess.Write))
    {
        workbook.Write(fileOut);
    }

    workbook.Close();
}

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

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

DioDocs for Excel

private const int RowCount = 100000;
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])";
}

NPOI

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

[Benchmark]
public void TestSetRangeFormulas()
{
    XSSFWorkbook workbook = new XSSFWorkbook();
    ISheet worksheet = workbook.CreateSheet("npoi");

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.CreateRow(r);
        for (int c = 0; c < 2; c++)
        {
            row.CreateCell(c).SetCellValue(r + c);
        }
    }

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.GetRow(r);
        for (int c = 2; c < ColumnCount + 2; c++)
        {
            ICell cell = row.CreateCell(c);
            CellReference reference1 = new CellReference(r, c - 2);
            CellReference reference2 = new CellReference(r, c - 1);
            cell.CellFormula = string.Format("SUM({0}, {1})", reference1.FormatAsString(), reference2.FormatAsString());
        }
    }

    workbook.Close();
}

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

DioDocs for Excel

private const int RowCount = 100000;
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();    
}

NPOI

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

[Benchmark]
public void TestCalcRangeFormulas()
{
    XSSFWorkbook workbook = new XSSFWorkbook();
    ISheet worksheet = workbook.CreateSheet("poi");

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.CreateRow(r);
        for (int c = 0; c < 2; c++)
        {
            row.CreateCell(c).SetCellValue(r + c);
        }
    }

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.GetRow(r);
        for (int c = 2; c < ColumnCount + 2; c++)
        {
            ICell cell = row.CreateCell(c);
            CellReference reference1 = new CellReference(r, c - 2);
            CellReference reference2 = new CellReference(r, c - 1);
            cell.CellFormula = string.Format("SUM({0}, {1})", reference1.FormatAsString(), reference2.FormatAsString());
        }
    }

    workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll();

    workbook.Close();
}

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

DioDocs for Excel

private const int RowCount = 100000;
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");
}

NPOI

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

[Benchmark]
public void TestSaveRangeFormulas()
{
    XSSFWorkbook workbook = new XSSFWorkbook();
    ISheet worksheet = workbook.CreateSheet("poi");

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.CreateRow(r);
        for (int c = 0; c < 2; c++)
        {
            row.CreateCell(c).SetCellValue(r + c);
        }
    }

    for (int r = 0; r < RowCount; r++)
    {
        IRow row = worksheet.GetRow(r);
        for (int c = 2; c < ColumnCount + 2; c++)
        {
            ICell cell = row.CreateCell(c);
            CellReference reference1 = new CellReference(r, c - 2);
            CellReference reference2 = new CellReference(r, c - 1);
            cell.CellFormula = string.Format("SUM({0}, {1})", reference1.FormatAsString(), reference2.FormatAsString());
        }
    }

    using (FileStream fileOut = new FileStream("./files/poi-saved-formulas.xlsx", FileMode.Create, FileAccess.Write))
    {
        workbook.Write(fileOut);
    }

    workbook.Close();
}

測定結果

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

DioDocs for Excel

DioDocs for Excelの結果

NPOI

NPOIの結果

「Mean(Arithmetic mean of all measurements)」の数値を比較してみると、NPOIよりもDioDocs for Excelの処理時間が短いことが確認できます。また、「Allocated」の数値を比較してみるとNPOIよりもDioDocs for Excelで測定した結果の方がメモリ使用量も少ないことも確認できます。さらに、大きなExcelファイルを読み込んで再計算を実施するTestCalcBigExcelFileの測定結果では、NPOIの場合はエラーが発生して結果がNAとなっています。

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

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

さいごに

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

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

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