Excel-DNA のセルに対する書き込み性能を計ってみた
ExcelDnaUtil.Application を F# から使ったらえらい遅かったので、色々試してみました。
結論を書くと、キャッシュしてなかったから遅かっただけでした。
ベンチマーク概要
100 * 100 のすべてのセルに 1 を設定していく時間を計測しました。
セルに対する書き込み性能を見るためのベンチマークなので、実際の使用状況では全く別の結果となることに注意してください。
ベンチマークの取り方
- https://github.com/bleis-tift/DNA からコードを取得する
- dna.sln をビルドする
- Excel を起動する
- dna_fsdll/bin/Release/dna_fsdll.xll を起動した Excel に DnD
- 「このアドインをこのセッションに限り有効にする」を選択
- dna_csdll/bin/Release/dna_csdll.xll を起動した Excel に DnD
- 「このアドインをこのセッションに限り有効にする」を選択
- リボンからアドインタブを選択
- 好きなベンチマークを走らせる
- ステータスバーにかかった時間が秒で表示される
ベンチマーク結果
コード | 結果 (秒) |
---|---|
F#(COM/キャッシュなし) | 362 |
F#(COM/キャッシュあり) | 2.65 |
F#(DNA) | 0.56 |
C#(COM) | 1.84 |
C#(DNA) | 0.55 |
VBA | 0.48 |
キャッシュなしの F#(COM) は VBA の 700 倍以上遅いという結果に。
まぁぶっ飛んだ値はとりあえず置いときましょう (なんでこんなに遅くなったのかは後述)。
傾向としては、セルに対する書き込み性能では
ということが言えるでしょう。
結論
セルへの書き込み速度が重要になるようなものの場合、COM ではなく Excel-DNA で用意されたオブジェクトを使うべきです。
最速は VBA ですが、単純にセルへの書き込みのみのマクロは少なく、何らかの計算などが行われるでしょう。
そうなると C# や F# で書いた方が高速になる (要出典) ため、VBA の出番は限られます。
使用したコード
C#
Range メソッドとかタプルの扱いとかがちょっと面倒でした。
ローカル変数以外で var が使えないのも残念です。
COM を使用したコードでは、dynamic が大活躍です。
Excel-DNA のオブジェクトを使用したコードでは、ExcelDnaUtil.Application をいったん dynamic で受ける必要があるのはちょっと面倒です。
static IEnumerable<Tuple<int, int>> range = from r in Enumerable.Range(1, 99) from c in Enumerable.Range(1, 99) select Tuple.Create(r, c); [ExcelCommand(MenuName = "Bench(C#)", MenuText = "COM Object")] public static void ComCs() { var sw = Stopwatch.StartNew(); dynamic excel = ExcelDnaUtil.Application; dynamic sheet = excel.ActiveSheet; foreach (var r_c in range) { dynamic cell = sheet.Cells.Item(r_c.Item1, r_c.Item2); cell.Value2 = 1; } sw.Stop(); excel.StatusBar = sw.Elapsed.TotalSeconds; } [ExcelCommand(MenuName = "Bench(C#)", MenuText = "DNA Object")] public static void DnaCS() { var sw = Stopwatch.StartNew(); foreach (var r_c in range) { var row = r_c.Item1 - 1; var col = r_c.Item2 - 1; var cell = new ExcelReference(row, col); cell.SetValue(1); } sw.Stop(); dynamic excel = ExcelDnaUtil.Application; excel.StatusBar = sw.Elapsed.TotalSeconds; }
F#
C# よりもやりたいことに集中できるのはいいのですが、dynamic に相当する仕組みを自分で作らなければならないのは面倒です。
幸い、gist に上げてくれていた人がいたため、ほぼそのまま使わせてもらいました。
が、これが dynamic に比べて遅い遅い・・・
C# の dynamic は裏でキャッシュされるので、上の仕組みにキャッシュ機能を組み込んだら 100 倍以上速くなりました。
let range = [ for r in [1..100] do for c in [1..100] -> (r, c) ] [<ExcelCommand(MenuName="Bench(F#)", MenuText="COM Object ??? Cache")>] let nonCachedCom () = let sw = Stopwatch.StartNew() let excel = ExcelDnaUtil.Application let sheet = excel?ActiveSheet range |> List.iter (fun (row, col) -> let cell = sheet?Cells?Item(row, col) cell?Value2 <- 1 ) sw.Stop() excel?StatusBar <- sw.Elapsed.TotalSeconds [<ExcelCommand(MenuName="Bench(F#)", MenuText="DNA Object")>] let dna () = let sw = Stopwatch.StartNew() range |> List.iter (fun (row, col) -> let row, col = row - 1, col - 1 let cell = ExcelReference(row, col) cell.SetValue(1) |> ignore ) sw.Stop() ExcelDnaUtil.Application?StatusBar <- sw.Elapsed.TotalSeconds