C#でExcel操作の処理時間を計測してみた  (Microsoft.Office.Interop.Excel)

# 計測した項目

1. セル取得 `Cells[,]`
2. セル値取得 `Range.Value`
3. セル値2取得 `Range.Value2`
4. セル値Text取得 `Range.Text`
5. セル値設定 `Range.Value = xxx`
6. セル範囲取得 `Range[Cells[,],Cells[,]]`
7. 罫線取得 `Range.Borders`
8. 罫線辺取得 `Borders[]`
9. 罫線辺設定 `Border.LineStyle = xxx`

# 測定結果

10000回実行したときの平均時間（ミリ秒）

|No | 内容           | ケースA | ケースB | ケースC |
|---:|---|---:|---:|---:|
| 1 | セル取得       | 1.024   | 0.975   |   5.221 |
| 2 | セル値取得     | 0.165   | 0.158   |   1.118 |
| 3 | セル値2取得    | 0.135   | 0.131   |   0.682 |
| 4 | セル値Text取得 | 0.203   | 0.196   |   1.170 |
| 5 | セル値設定     | 0.301   | 0.302   |   0.992 |
| 6 | セル範囲取得   | 4.288   | 4.265   |  20.211 |
| 7 | 罫線取得       | 0.968   | 0.882   |   5.012 |
| 8 | 罫線辺取得     | 0.915   | 0.919   |   5.299 |
| 9 | 罫線辺設定     | 0.873   | 0.834   |   3.391 |

- ケースA ・・・ 新規ブックに対して実行
- ケースB ・・・ ケースA実行後のブックに対して実行
- ケースC ・・・ 約10000×20個のデータが入力されているブック（ケースA,Bをまとめ作業してたブック）に対して実行（下記[^1]）

[^1]: 実行した際は、行(11～10002行目)を非表示にはしていない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/92d5714a-2800-25fb-5e5d-d7f6b80ffbdf.png)

## 測定結果に対する考察

- セル範囲取得や罫線情報へのアクセスに時間がかかるようである。
- 全ての項目でケースCの時間がケースA,Bよりもかかっており、大量のデータを含むExcelファイルに対しては各種操作の実行時間がかなり伸びるようである。

# 回数方向の傾向をみてみる

10000回の実行中に実行時間が伸びていくのかを見てみた。
結果としては、伸びてはいかないが、時々極端に時間がかかるタイミングがある。
メモリ解放処理(GC)が走っている？

下図は、ケースCの、項目6 セル範囲取得 の結果をプロットしたもの。※縦軸の単位は[秒]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/63eb55f3-88d2-bde5-fd0e-3c2df68f33fe.png)



# 測定用コード

```csharp

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices; // to use [MethodImpl(MethodImplOptions.NoInlining)]
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

class ExcelSample
{
    static void DumpElapsSec(string mark, Stopwatch sw)
    {
        double sec = (double)sw.ElapsedTicks / (double)Stopwatch.Frequency;
        Console.Write(mark+"\t");
        Console.WriteLine(sec);

    }


    //[MethodImpl(MethodImplOptions.NoInlining)]
    [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] // 最適化抑制
    static void OverwriteActiveSheet()
    {
        Excel.Application oExcelApp = null;
        Excel.Worksheet oSheet = null;
        
        try {
            oExcelApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
        }
        catch ( System.Runtime.InteropServices.COMException ) {
            Console.WriteLine("Failed to get Excel.Application.");
            return;
        }
        
        try {
            var oBook = (Excel.Workbook)oExcelApp.ActiveWorkbook;
            if ( oBook != null ) {
                oSheet = (Excel.Worksheet)oBook.ActiveSheet;
            }
        }
        catch ( System.Runtime.InteropServices.COMException ) {
        }
        if ( oSheet == null ) {
            Console.WriteLine("Failed to get ActiveWorkbook or ActiveSheet.");
            return;
        }


        var sw = new Stopwatch();
        bool screenUpdatingBackup = oExcelApp.ScreenUpdating;

        Excel.XlCalculation calcModeBackup = oExcelApp.Calculation;
        try {
            oExcelApp.ScreenUpdating = false; // 画面更新の停止
            oExcelApp.Calculation = Excel.XlCalculation.xlCalculationManual; // 再計算の停止
            

            // セルの読み込み
            Excel.Range oCells = oSheet.Cells;
            

            for ( int row=1;row<=10000;row++ ){
                sw.Restart();
                var oRange = oCells[row, 2] as Excel.Range; // B列row行セル
                sw.Stop();
                DumpElapsSec("1", sw);

                sw.Restart();
                object a = oRange.Value;
                sw.Stop();
                DumpElapsSec("2", sw);

                sw.Restart();
                object b = oRange.Value2;
                sw.Stop();
                DumpElapsSec("3", sw);

                sw.Restart();
                object c = oRange.Text;
                sw.Stop();
                DumpElapsSec("4", sw);
                //Console.Error.WriteLine(a);
                //Console.Error.WriteLine(b);
                //Console.Error.WriteLine(c);

                // セルへの書き込み
                oRange = oCells[row, 3] as Microsoft.Office.Interop.Excel.Range;
                sw.Restart();
                oRange.Value = "hoge";
                sw.Stop();
                DumpElapsSec("5", sw);

                // 罫線を引く
                Excel.Borders oBorders;
                Excel.Border oBorder;
                sw.Restart();
                oRange = oSheet.Range[oSheet.Cells[row, 2], oSheet.Cells[row+2, 4]];
                sw.Stop();
                DumpElapsSec("6", sw);

                sw.Restart();
                oBorders = oRange.Borders;
                sw.Stop();
                DumpElapsSec("7", sw);

                sw.Restart();
                oBorder = oBorders[Excel.XlBordersIndex.xlEdgeBottom];// xlEdgeLeft xlEdgeRight xlEdgeTop
                sw.Stop();
                DumpElapsSec("8", sw);

                sw.Restart();
                oBorder.LineStyle = Excel.XlLineStyle.xlContinuous;
                sw.Stop();
                DumpElapsSec("9", sw);
            }
        }
        finally {
            if ( oExcelApp.Calculation != calcModeBackup ) {
                oExcelApp.Calculation = calcModeBackup;
            }
            if ( oExcelApp.ScreenUpdating != screenUpdatingBackup ) {
                oExcelApp.ScreenUpdating = screenUpdatingBackup;
            }
        }
    }

    [STAThread]
    static void Main(string[] args)
    {
        OverwriteActiveSheet();
    }
}
```

# 上記の出力データの整形用コード

```csharp

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;


class FilterTextRow
{
    static readonly int N = 9;

    [STAThread]
    static void Main(string[] args)
    {
        if(args.Length==0){return;}

        Regex r = new Regex("^([0-9]+)\t(.*)$");

        string[] lines = File.ReadAllLines(args[0]);

        var sb = new StringBuilder();
        for(int i=0;i+N-1<lines.Length;i+=N){
            for(int k=0;k<N;k++){
                Match m = r.Match(lines[i+k]);
                //if(m.Success){
                    if (k>0){sb.Append("\t");}
                    sb.Append(m.Groups[2].Value);
                //}// else error
            }
            sb.Append("\n");
        }
        Console.Write(sb.ToString());
    }
}
```

# 参考サイト

- [C#からExcel操作 テンプレ - ライブラリレス（※要Excel） - Qiita](https://qiita.com/kob58im/items/780d5b7ddb854b57e98b)
- [MethodImplOptions Enum (System.Runtime.CompilerServices) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.methodimploptions?view=net-5.0)
- [処理時間を正確に計測するには？［2.0のみ、C#、VB］ - ＠IT](https://www.atmarkit.co.jp/fdotnet/dotnettips/412stopwatch/stopwatch.html)

# 以下は、記事を書いた後に見つけたサイト

- [Excel書き込みの実行速度調査について - Qiita](https://qiita.com/s_Pure/items/d92e5988d8207f5adb89)