Excel書き込みの実行速度調査について

# Excel書き込みの実行速度について
C++でExcelを高速に操作したい！との思いから、様々なAPIの実行速度を測定してみました。
今回、利用したい機能は、書き込みなので、書き込み時間しか調査していません。

■40200回セルに書き込みを行った結果


言語 | 手法 | xls | xlsx | 実行時間
-------------|------------- | ------------- | ------------- | -------------
C# | NPOI (xls) | ○ | × | 96.4 ms
C# | EPPlus (xlsx) | × | ○ | 243.2 ms
C++ | ExcelCreater (Ver 3.0.3.44.12) | ○ | × | 434.7 ms
Python | xlwt (読み込みはxlrd)  | ○ | × | 458.6 ms
C# | NPOI (xlsx) | × | ○ | 582.0 ms
C# | ClosedXML | × | ○ | 667.2ms
Python | openpyxl | × |  ○ | 965.6 ms
C++ | ExcelCreater (Ver 2.0.8.1) | ○ | × | 8040.5 ms
C++ | Microsoft Excel 11.0 Object Library | ○ | × | 14171.9 ms
C# | OpenXML | × | ○ | 2363686 ms

## 各APIの実行時間について
.NET系のAPIは、初回実行時に「平均の時間＋300ms」ほど時間がかかっていました。
そのため、初回の実行時間を除いた50回の平均時間を算出しています。

### .NETアプリの実行テクニック
初回実行時に時間がかかる問題を解決するために、以下の2点についても調査してみようと思います。
1. .NET起動時に読み込む
2. ネイティブ展開
「C# .NET 速度」←検索キーワード

## OpenXMLの実行時間について
目を疑うほど遅かったです。もしかすると、実装方法に問題が有るのかもしれません。

## まとめ
### ・「xls」と「xlsx」の最速を目指す場合

言語 | 手法 | xls | xlsx | 実行時間
-------------|------------- | ------------- | ------------- | -------------
C# | NPOI (xls) | ○ | × | 96.4 ms
C# | EPPlus (xlsx) | × | ○ | 243.2 ms

### ・使用モジュールを１つにする場合

言語 | 手法 | xls | xlsx | 実行時間
-------------|------------- | ------------- | ------------- | -------------
C# | NPOI (xls) | ○ | × | 96.4 ms
C# | NPOI (xlsx) | × | ○ | 582.0 ms

### 今後について
xlsxのみの対応で良かったので、EPPlusを利用する事にしました。
C++から呼び出すことができる、エクセル操作DLLをC#で作成しようと思います。

## NPOI
### 導入方法
1. NuGet パッケージの管理の画面から **NPOI** をインストールします。
　※NuGetが最新で無い場合はエラー発生の可能性有り。更新してください。
2. 「 **using NPOI;** 」が読み込めればインストール完了です。

### 使用方法
PDFにまとまっている資料です。
 [NPOI の使い方](https://sowel.co.jp/PDF_file/CS/External_Library/NPOI_HowToUse.pdf) 

### NPOIのコード例
```cs:NPOI.cs
        public static void Check(string Ver)
        {
            int length = 200;

            try
            {
                string filePath = Directory.GetCurrentDirectory() + "\\NPOI_XML." + Ver;

                //ブック作成
                var book = CreateNewBook(filePath);

                //シート無しのexcelファイルは保存は出来るが、開くとエラーが発生する
                book.CreateSheet("Sheet");

                //シート名からシート取得
                var sheet = book.GetSheet("Sheet");

                //セルに設定
                WriteCell(sheet, 0, 0, filePath);

                //
                for (int i = 0; i < length; i++)
                {
                    WriteCell(sheet, 0, i + 1, "【出力確認】");
                    for (int j = 0; j < length; j++)
                    {
                        WriteCell(sheet, j + 1, i + 1, "【確認】i：" + i + "  j：" + j);
                    }
                }

                //ブックを保存
                using (var fs = new FileStream(filePath, FileMode.Create))
                {
                    book.Write(fs);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

                //ブック作成
        static IWorkbook CreateNewBook( string filePath )
        {
            IWorkbook book;
            var extension = Path.GetExtension( filePath );

            // HSSF => Microsoft Excel(xls形式)(excel 97-2003)
            // XSSF => Office Open XML Workbook形式(xlsx形式)(excel 2007以降)
            if( extension == ".xls" ) {
                book = new HSSFWorkbook();
            }
            else if( extension == ".xlsx" ) {
                book = new XSSFWorkbook();
            }
            else {
                throw new ApplicationException( "CreateNewBook: invalid extension" );
            }

            return book;
        }
        //セル設定(文字列用)
        public static void WriteCell( ISheet sheet, int columnIndex, int rowIndex, string value )
        {
            var row = sheet.GetRow( rowIndex ) ?? sheet.CreateRow( rowIndex );
            var cell = row.GetCell( columnIndex ) ?? row.CreateCell( columnIndex );

            cell.SetCellValue( value );
        }
```

## EPPlus
### 導入方法
1. NuGet パッケージの管理の画面から **EPPlus** をインストールします。  
　[見つからない場合は、NuGetの設定を確認してください。](#nuget設定方法)  
　※NuGetが最新で無い場合はエラー発生の可能性有り。更新してください。
2. 「 **using OfficeOpenXml;** 」が読み込めればインストール完了です。

### 使用方法
とても分かりやすく解説してくれています。
 [EPPlusの使い方](https://chawatoyo.blog.fc2.com/blog-entry-15.html) 

### NuGet設定方法
「オプション」→「NuGetパッケージマネージャー」→「パッケージソース」に
* nuget.org
https://www.nuget.org/api/v2/
を追加する。




## ClosedXML
###導入方法

1. NuGetの更新
[ClosedXMLのインストール時のエラー](http://itidai.xsrv.jp/visual-studio/closedxml%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%99%82%E3%81%AE%E3%82%A8%E3%83%A9%E3%83%BC/) 
→サイトにはNuGetのバージョンを落とせと書いてあるけど、ClosedXML自体のバージョンを落としてもインストールできます。

2. NuGet で「ClosedXML」をインストール
 [Excelの取り扱いにClosedXMLを使用する](http://atamo-dev.hatenablog.com/entry/2017/07/23/180026) 
 
### ClosedXMLのコード例
```cs:ClosedXML.cs
    class Excel_CHK
    {
        public void Check()
        {
            int length = 200;
            string path = Directory.GetCurrentDirectory() + "\\Check_ClosedXML.xlsx";
            var workbook = new XLWorkbook();
            var worksheet = workbook.Worksheets.Add("Sheet");
            worksheet.Cell("A1").Value = path;
            for (int i = 0; i < length; i++)
            {
                worksheet.Cell(i + 2, 1).Value = "【出力確認】";
                for (int j = 0; j < length; j++)
                {
                    worksheet.Cell(i + 2, j + 2).Value = "【確認】i：" + i + "  j：" + j;
                }
            }
            workbook.SaveAs(path);
        }
    }
```




## openpyxl
### 導入方法
1. 実行したい環境に『pip install OpenPyXL』を行う。
2. 「 **import openpyxl as px** 」が実行できれば正常に導入できています。

### コード例
```python:openpyxl.py
import openpyxl as px
import time
%%time

length = 200

# ファイル名の指定
filename = 'openpyxl_Speed_Check.xlsx'

# ブックの新規作成
wb = px.Workbook()

# シートの選択
ws = wb['Sheet']

# ファイル名の書き込み
ws["A1"].value = filename

# データの書き込み
for i in range(length):
    ws.cell(row=i+2, column=1).value = '【出力確認】'
    for j in range(length):
        ws.cell(row=i+2, column=j+2).value = '【確認】i：{}  j：{}'.format(i, j)
    

# ファイル保存
wb.save(filename)
```

## xlwt
書き込みのみできるPythonライブラリです。
読み込みライブラリは、『xlrd』です。

### 導入方法
1. 実行したい環境に『pip install xlwt』を行う。
2. 「 **import xlwt** 」が実行できれば正常に導入できています。

### コード例
```py:xlwt.py
import xlwt
%%time
wb = xlwt.Workbook()
sheet = wb.add_sheet('Sheet')
# ファイル名の指定
filename = 'xlwt_Speed_Check.xls'

# ファイル名の書き込み
sheet.write(0, 0, filename)

# データの書き込み
for i in range(length):
    sheet.write(i+1, 0, '【出力確認】')
    for j in range(length):
        Temp = '【確認】i：{}  j：{}'.format(i, j)
        sheet.write(i+1, j+1,Temp)
    

# ファイル保存
wb.save(filename)
```


## OpenXML
### 導入方法
1. Open XML SDK 2.0 for Microsoft Officeのインストール  
　ライブラリのインストーラーを取得し、インストールを行います。  
　http://www.microsoft.com/ja-jp/download/details.aspx?id=5124
2. インストールで生成されたdllファイルをプロジェクトフォルダに移動
　インストール先に指定したフォルダ内の「DocumentFormat.OpenXml.dll」をプロジェクトのフォルダにコピーします。
3. 「 **using DocumentFormat.OpenXml;** 」が実行できればインストール完了です。

### コード例
```cs:OpenXML.cs
    public void Check()
    {
        int length = 200;
        string ExcelFilePath = Directory.GetCurrentDirectory() + "\\Check_OpenXML.xlsx";
        // Excelファイルを作る
        using (SpreadsheetDocument spreadsheetDocument
          = SpreadsheetDocument.Create(ExcelFilePath, SpreadsheetDocumentType.Workbook))
        {
            // ワークブックを用意し、ワークシートを追加する
            WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart();
            workbookpart.Workbook = new Workbook();
            WorksheetPart worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
            worksheetPart.Worksheet = new Worksheet(new SheetData());
            Sheets sheets
              = spreadsheetDocument.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets());
            Sheet sheet = new Sheet()
            {
                Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart),
                SheetId = 1,
                Name = "Sheet"
            };
            sheets.Append(sheet);

            // セルA1をワークシートに追加する
            writework(1, 1, worksheetPart, ExcelFilePath, CellValues.String);
            for (uint i = 0; i < length; i++)
            {
                writework(1, i + 2, worksheetPart, "【出力確認】", CellValues.String);
                for (uint j = 0; j < length; j++)
                {
                    writework(j + 2, i + 2, worksheetPart, "【確認】i：" + i + "  j：" + j, CellValues.String);
                }
            }

            // ワークブックを保存する
            workbookpart.Workbook.Save();
        }
    }

    private static void writework(uint col, uint row, WorksheetPart WSP, string val, CellValues type)
    {
        Cell cellt = InsertCellInWorksheet(ToAlphabet(col), row, WSP);
        cellt.CellValue = new CellValue(val);
        cellt.DataType = new EnumValue<CellValues>(CellValues.String);
    }

    private static Cell InsertCellInWorksheet(string columnName, uint rowIndex, WorksheetPart worksheetPart)
    {
        Worksheet worksheet = worksheetPart.Worksheet;
        SheetData sheetData = worksheet.GetFirstChild<SheetData>();
        string cellReference = columnName + rowIndex.ToString();
        // 指定された位置のRowオブジェクト
        Row row
          = sheetData.Elements<Row>().FirstOrDefault(r => r.RowIndex == rowIndex);
        if (row == null)
        {
            // Rowオブジェクトがまだ存在しないときは作る
            row = new Row() { RowIndex = rowIndex };
            sheetData.Append(row);
        }
        // 指定された位置のCellオブジェクト
        Cell refCell
          = row.Elements<Cell>().FirstOrDefault(c =>
              c.CellReference.Value == cellReference);
        if (refCell != null)
            return refCell; // すでに存在するので、それを返す
        // Cellオブジェクトがまだ存在しないときは作って挿入する
        Cell nextCell
          = row.Elements<Cell>().FirstOrDefault(c =>
              string.Compare(c.CellReference.Value, cellReference, true) > 0);
        Cell newCell = new Cell() { CellReference = cellReference };
        row.InsertBefore(newCell, nextCell);
        worksheet.Save();
        return newCell;
    }
    public static string ToAlphabet(uint columnNo)
    {
        string alphabet = "ZABCDEFGHIJKLMNOPQRSTUVWXY";
        string columnStr = string.Empty;
        int m = 0;
        do
        {
            m = (int)columnNo % 26;
            columnStr = alphabet[m] + columnStr;
            columnNo = columnNo / 26;
        } while (0 < columnNo && m != 0);
        return columnStr;
    }

```