Office 開発におけるパフォーマンス トラブルシュート (その 1:概要と対処方法)

Last Update:

(※ 2018 年 6 月 20 日に Japan Office Developer Support Blog に公開した情報のアーカイブです。)

こんにちは、Office 開発サポート チームの中村です。

2018/7/12 Update
ボトルネック特定手法についてのパート 2 記事を公開しました。

Office 開発において、「期待するパフォーマンスが出ない」「利用する Office のバージョンによってパフォーマンスが低下する」といったお問い合わせを頂くことがあります。今回の記事では、このような状況で改善を図るためにどのように調査・検討を進めればよいかを記載します。

今回は全体の流れと Office におけるパフォーマンスの考え方などを紹介しています。ボトルネックの特定手法については、以下のパート 2 の記事をご参照ください。

タイトル : Office 開発におけるパフォーマンス トラブルシュート (その 2 : ボトルネックの特定)
アドレス : https://officesupportjp.github.io/blog/Office 開発におけるパフォーマンス トラブルシュート (その 2:ボトルネックの特定)/

目次
1. はじめに : Office におけるパフォーマンス
2. プログラム上のボトルネックの特定
3. 対処方法の検討

3-1. ボトルネックとなる処理の速度改善
3-2. プログラム構成の見直し
3-3. プログラム全体のパフォーマンス チューニング
3-4. 他の言語から VBA に変更する
3-5. マシン リソースの増強

 

1. はじめに : Office におけるパフォーマンス

Office はデスクトップ アプリケーションとして設計されています。このため、ユーザー操作では大きく気にならないような短い処理時間のパフォーマンスは、機能設計上それほど優先される考慮事項ではありません。このため、例えばデータベース製品のように、大量の要求が常に送られれてくることを想定した精度のパフォーマンス設計は行われていません。プログラムにより大量の処理を繰り返し行うと、ユーザー操作では許容される程度の小さな積み重ねが全体としてパフォーマンスに大きく影響を与えることがあります。

また例えば、「Office 2010 から Office 2016 にバージョンアップしたら以前の処理時間の 3 倍になった」といったご報告を頂くことがあります。しかしながら、これは製品の設計上、やむを得ない変更であることが多いです。

アプリケーションのバージョンアップを行うと、より新しい製品だからパフォーマンスも向上しているだろう、と考えられることがありますが、必ずしもそうではありません。セキュリティ脆弱性への対応、機能拡張、不具合の修正などによって、どちらかといえば Office プログラム はバージョンを重ねるごとに複雑化していきます。同じメソッドの呼び出しであっても、そのメソッドにより実行される Office 内部の実装が複雑になる (コード量が増える) と、パフォーマンスは低下します。

例えば、ファイルの Open メソッドが以前のバージョンの Office より遅くなるという場合、ファイルを開く際にそのファイルがセキュリティ上問題ないかのチェック処理の追加、新しく追加されたリッチな表現の描画などの影響で遅くなる、といったイメージです。
パフォーマンス低下が著しい場合には、Office 製品としても可能な限りパフォーマンス向上の対応を行います。ただ、これは単純に処理時間が x 倍になった、というだけではなく、実際の時間としてユーザー利用で耐えうる処理時間であるか、また、その処理がどの程度一般的に利用されるか、といった観点から判断しています。先述のように、ユーザー操作では気にならない程度の低下と判断されるような場合は、ある程度のパフォーマンス低下は仕様上想定されたものとなります。

先程の例でいえば、ファイル オープンが 1 秒から 3 秒に延びたとします。ファイルのオープンは一般的な操作ですが、ユーザー操作で開いては閉じ、を短時間で繰り返し行うことは稀で、1 度ファイルを開いたらしばらくそのファイル上で作業を行うことが一般的と考えられます。このような状況で、1 回のオープンあたり 2 秒の増加はユーザー操作上大きく使用感を損なうほどではなく、機能向上とのトレードオフとして許容される場合がある、ということです。

本記事では、このような背景をふまえた上で、パフォーマンス向上を図るための調査手法、とり得る対応方法について記載します。個別の現象は、弊社サポートでないと判断が難しい部分もありますが、調査や対処の進め方のイメージを掴んでいただいたり、サポートのご利用が難しい開発者の皆様へ、少しでも解決にお役立ていただければと思います。

ここからは、「Office バージョンアップに伴い、これまで利用していたプログラムのパフォーマンスが低下した」というシナリオを想定して記載していきます。

 

2. プログラム上のボトルネックの特定

ビジネスで利用される Office を利用するプログラムは多くの場合、数百~数千行に及ぶ複雑なプログラムで構成されています。Office をバージョンアップすると、このプログラム全体としてパフォーマンスが以前より低下することが分かったとします。ここで次に行うべき作業は、ボトルネックとなる処理の特定です。

特定方法として、プログラム処理の各所で時刻付きのデバッグ ログを出力することが有効です。このようなログ出力処理を追加してプログラムを実行し、ログからどの処理で時間がかかっているのかを確認していきます。ここでは、Workbooks.Open が遅い、Range(“A1”).Value = “aaa” が遅い、といったように 1 つの関数レベルまで絞り込むのが理想です。そうすると、次の対処方法が検討しやすくなります。

とはいえ、大きなプログラムにデバッグ ログを追加する作業は容易ではないと思います。これについては、次の記事でログ出力処理を自動追加するコードのサンプル (VBA 向け) や、具体的な特定の進め方について詳しく記載しています。

 

3. 対処方法の検討

ボトルネックとなっている処理の特定を行ったら、次に、その処理のパフォーマンス向上が可能かの調査を行います。パフォーマンスを向上させる方法として取り得るアプローチはいくつか考えられます。

 

3-1. ボトルネックとなる処理の速度改善

ボトルネックとなる処理によっては、例えばメソッドの呼び出し引数の変更、類似の別の関数の利用、Office のオプション設定の変更、文書やワークシートなどの構成の見直しなどの方法によってパフォーマンスが改善できる場合があります。

ただ、何が有効であるかは、ボトルネックとなる処理によって様々なので一概にお伝えできず、関数のヘルプを見ながら動作が変わりそうな引数を試す、オプション設定を見て影響がありそうな設定を変更してみる、というように、推測を基に試していただくことになります。既知の現象として、サポート技術情報などで情報が公開されている場合もあります。

 

3-2. プログラム構成の見直し

ボトルネックとなる処理自体を速くすることは難しい場合、その処理の実行回数を減らしたり、同じ結果を得られる別の実装方法に変更するといった方法が検討できます。

例えば、Excel でセルに値を設定する処理がボトルネックとなっており、現在の実装では数千個のセルに 1 つずつループ処理で値を書き出しているような場合を考えます。この処理を、二次元配列にセルに書き出すデータを全て格納した上で、配列のデータを一括してセルに出力する実装に変更すると、ボトルネックとなる処理の実行回数が数千回から 1 回になり、パフォーマンスは向上します。

参考) 二次元配列を用いたセル操作については公開情報にサンプル コードなどの記載があります。

タイトル : Visual Basic .NET で Excel を自動化し、配列による範囲内へのデータ入力および範囲内からのデータ取得を行う方法
アドレス : https://support.microsoft.com/ja-jp/kb/302094 

タイトル : Visual C# で Excel を自動化して、配列による範囲内へのデータ入力および範囲内からのデータ取得を行う方法
アドレス : https://support.microsoft.com/ja-jp/kb/302096

また別の例として、セルへの値書き込みが、今回の処理結果を次回起動時に引き継ぐためのプログラム用の情報保持が目的である、といった場合、必ずしもセルに書き込む必要はありません。ブックのプロパティに書き込む、他のテキストファイルに出力する、などの保持方法に変更するといったアイデアもあります。(このような方法が実際にパフォーマンス向上となるかは状況によります。あくまで、考え方の例となります。)
このように、セルへの値書き込み処理自体を速くすることはできない場合も、その処理によって実現したい目的を満たす他の実装方法にすることでパフォーマンス向上を図るアプローチが検討できます。

 

3-3. プログラム全体のパフォーマンス チューニング

ボトルネック箇所の変更が難しい場合、プログラムの他の箇所のパフォーマンスを向上させることで全体の処理時間を短縮し、パフォーマンス要件を満たす方法も検討できます。

例えば、3-2. で記載した配列を用いたセル操作は、それ自体がボトルネックでない場合にも、大量のセル操作を含むプログラムのパフォーマンス改善に大きく貢献できます。また例えば、Application.ScreenUpdating プロパティをプログラムの処理開始時に False に設定すると、プログラム実行中の描画処理が抑止され、一般的にパフォーマンスが向上します。

このような一般的なパフォーマンス向上施策については、以下の公開情報でご紹介しています。(情報が古いバージョンの Office 向けのものもありますが、ほとんどの内容が Office バージョンによらず有効です。)

タイトル : Excel のパフォーマンス: パフォーマンスの障害物を最適化するためのヒント
アドレス : https://msdn.microsoft.com/ja-jp/vba/excel-vba/articles/excel-tips-for-optimizing-performance-obstructions

タイトル : 少ないメモリを使用するように、Excel ブックをクリーンアップする方法
アドレス : https://support.microsoft.com/ja-jp/help/3070372 (日本語機械翻訳版)
アドレス : https://support.microsoft.com/en-us/help/3070372 (英語版)

タイトル : Word 2007 と Word 2010 を最適化する方法
アドレス : https://support.microsoft.com/ja-jp/help/918793 (日本語機械翻訳版)
アドレス : https://support.microsoft.com/en-us/help/918793 (英語版)

タイトル : プレゼンテーションのパフォーマンスを向上させるためのヒント
アドレス : https://support.office.microsoft.com/ja-jp/article/34c82835-5f23-4bf0-98cc-72235bbd2949

タイトル : Access のパフォーマンスを向上させる
アドレス : https://support.office.microsoft.com/ja-jp/article/f6827763-bb5c-4f48-8457-7a14addab6be

 

3-4. 他の言語から VBA に変更する

Office のオートメーションにおいては、同じプログラムを VBA と、.NET や C++ などを用いて記述した場合では、VBA の方が若干高速です。これは、以下のような理由によります。

  • Office では、プログラム処理の最後に画面描画やデータの同期等、処理に伴う結果を Office 全体に反映させるクリーンナップ処理を行います。VBA で実装した場合は、Office は「一連の処理」がどこからどこまでか範囲を認識可能なため、処理全体が完了したときにクリーンナップ処理を行います。一方、他の言語を用いてオートメーションされる場合は、「一連の処理」の範囲を認識できません。このため、1 つ 1 つの処理呼び出し (メソッド呼び出し 1 回、プロパティ変更 1 回、など) ごとにクリーンナップ処理を行います。このため、他の言語では VBA と比較して非常に多くのクリーンナップ処理が呼び出され、その分のパフォーマンス コストがかかります。

  • 他の言語から Office の COM オブジェクトへの処理呼び出しに伴い、マーシャリングが行われます。僅かなコストではありますが、各処理においてマーシャリングが行われることの積み重ねでパフォーマンスに影響を与えます。VBA の場合は、内部呼び出しのためこのコストはかかりません。(特に .NET プログラムからの呼び出しの場合は、ランタイム呼び出し可能ラッパー (RCW) によるマーシャリングが行われ、アンマネージド コードと比較するとさらに低速となります。)

    参考)

    タイトル : ランタイム呼び出し可能ラッパー
    アドレス : https://docs.microsoft.com/ja-jp/dotnet/framework/interop/runtime-callable-wrapper

  • .NET プログラムの場合は、以下の資料で説明するように適切なタイミングで COM オブジェクトの解放が必要です。ガベージ コレクトはパフォーマンス コストの高い処理のため、これを適宜呼び出すことによって、(適切な実装が行われた) .NET プログラムではプログラム全体としてパフォーマンスが低下します。

    タイトル : Office オートメーションで割り当てたオブジェクトを解放する – Part1
    アドレス : https://officesupportjp.github.io/blog/Office オートメーションで割り当てたオブジェクトを解放する – Part1/

    タイトル : Office オートメーションで割り当てたオブジェクトを解放する – Part2
    アドレス : https://officesupportjp.github.io/blog/Office オートメーションで割り当てたオブジェクトを解放する - Part2/

以上のような理由から、もし既存コードが他の言語であるなら、VBA で記述することでパフォーマンス改善を図ることができます。完全に VBA のみで行うことは難しい場合も、例えばユーザー インターフェイスとなる Windows フォームは .NET で作成し、ボタンクリック イベントから Application.Run メソッドを用いて Office ファイル内の VBA を呼び出し、実際の処理の内容は VBA で実行するといった仕組みを検討できます。

 

3-5. マシン リソースの増強

ここまでに述べたようなプログラム的なアプローチが難しい場合は、CPU 処理速度やディスク アクセスの向上、メモリ増強でページングの発生を抑えるなどのリソース面からのアプローチで、パフォーマンス改善を図ることをご検討ください。このような方向での対応を検討する場合は、ここまでの調査とは異なり、プログラム処理実行中のリソースの推移をパフォーマンス モニターなどのツールを用いて確認し、ボトルネックとなるリソースを確認し、そのリソースを増強します。

結局のところ、具体的な対応方法についてはケース バイ ケースとなりますが、上記をご参考に調査・検討を進めてみていただければ幸いです。

弊社でのご支援をご希望の際はぜひプレミア サポートへお問い合わせいただければと思います。プレミア サポートでは、2. のボトルネックの特定のご支援からお承りしています。

今回の投稿は以上です。

本情報の内容 (添付文書、リンク先などを含む) は、作成日時点でのものであり、予告なく変更される場合があります。