Excel でのマルチスレッド再計算
適用対象: Excel 2013 | Office 2013 | Visual Studio
Microsoft Office Excel 2007 は、ワークシートのマルチスレッド再計算 (MTR) を使用する Excel の最初のバージョンでした。 コンピューターのプロセッサ数やプロセッサのコア数に関係なく、再計算時に最大 1024 個の同時実行スレッドを使用するように Excel を構成できます。
注:
それぞれのスレッドには関連するオペレーティング システムのオーバーヘッドがあるため、必要以上に多くのスレッドを使用するように Excel を構成してはいけません。
コンピューターに複数のプロセッサまたはプロセッサのコアが搭載されている場合は、オペレーティング システムが、最適な方法でプロセッサにスレッドを割り当てます。
Excel の MTR の概要
Excel は、計算チェーンの各部分のうち、別々のスレッドで並行的に再計算できる部分を識別しようとします。 次の非常に単純なツリー (x ← y は、y が x にのみ依存していることを意味する) は、この例を示しています。
図 1. 複数のスレッドでの同時並行計算
する
A1 の計算が済むと、一方のスレッドでは A2、A3 の順に計算ができるようになり、同時に、もう一方のスレッドでは B1、C1 の順に計算ができるようになります (すべてのセルがスレッド セーフであると仮定しています)。
注:
スレッド セーフなセルという用語は、スレッド セーフな関数のみを含むセルを意味します。 スレッド セーフとは何で、スレッド セーフではないのかについて詳しくは、「 Excel でスレッド セーフと見なされるものと見なされないもの」を参照してください。
ほとんどの実用的なブックには、この例よりもはるかに複雑な依存関係ツリーが含まれています。 さらに、セルの再計算時間は計算が完了するまで認識できず、関数の引数によって大きく異なる場合があります。 最良の結果を得るために、Excel は、それ以上の最適化ができなくなるまで、計算のたびに計算順序の改善を試みます。
Excel は、次の項目の実行に単一のメイン スレッドを使用します。
組み込みコマンド
XLL コマンド
アドイン マネージャー インターフェイス関数 (xlAutoOpen 関数など)
Microsoft Visual Basic for Applications (VBA) のユーザー定義コマンド (通常はマクロと呼ばれます)
VBA のユーザー定義関数
組み込みのスレッド セーフではないワークシート関数 (次のセクションに一覧を示します)
XLM マクロ シートのユーザー定義コマンドとユーザー定義関数
COM アドインのコマンドと関数
条件付きフォーマット式内の関数と演算子
ワークシートの式で使用された定義済みの名前定義内の関数と演算子
数式編集ボックス内の式の強制的な評価 (F9 キーを使用)
Excel が複数のスレッドを使用するように構成されていない限り、関数がスレッド セーフかどうかに関係なく、すべてのワークシート数式がメイン スレッドで評価されます。 ユーザーが複数のスレッドを使用することを指定すると、スレッド セーフなセルに追加のスレッドが使用されます。 負荷分散の観点から見ると、スレッド セーフなセルには、メイン スレッドが引き続き使用される可能性があることに注意してください。
ここでもう一度強調すべきこととして、Excel は一度に複数のコマンドを実行しません。そのため、スレッド ローカル メモリとクリティカル セクションの使用など、スレッド セーフな関数を記述するときと同様の注意を払う必要はありません。
Excel でスレッド セーフと見なされるもの (見なされないもの)
Excel では、次のものだけがスレッド セーフと見なされます。
Excel の単項演算子と二項演算子のすべて
Excel 2007以降のほぼすべての組み込みワークシート関数(例外リストを参照)
スレッド セーフとして明示的に登録されている XLL アドイン関数
次の組み込みワークシート関数は、スレッド セーフではありません。
PHONETIC
"format"または "address"引数が使用されている場合はCELL
INDIRECT
GETPIVOTDATA
CUBEMEMBER
CUBEVALUE
CUBEMEMBERPROPERTY
CUBESET
CUBERANKEDMEMBER
CUBEKPIMEMBER
CUBESETCOUNT
ADDRESS (5 番目のパラメーター "sheet_name" が指定されている場合)
ピボットテーブルを参照するデータベース関数 (DSUM、DAVERAGE など)
ERROR.TYPE
HYPERLINK
明確にするために、スレッド セーフと見なされないものを挙げます。
VBA のユーザー定義関数
COM アドインのユーザー定義関数
XLM マクロ シートのユーザー定義関数
明示的にスレッド セーフとして登録されていない XLL アドイン関数
次の操作と関数はスレッド セーフとは見なされません。また、スレッド セーフとして登録した XLL 関数から呼び出されると失敗します。
XLM 情報関数 (xlfGetCell (GET.CELL) など) の呼び出し。
XLL 内部名の定義または削除のための xlfSetName (SET.NAME) の呼び出し。
スレッド セーフではないユーザー定義関数の xlUDF を使用した呼び出し。
スレッド セーフではない関数を含む式、またはスレッド セーフではない関数を含む定義によって定義された名前を含む式に対する xlfEvaluate 関数の呼び出し。
ブレーク条件をクリアする xlAbort 関数の呼び出し。
計算されていないセルの参照の値を取得する xlCoerce 関数の呼び出し。
注:
XLLワークシート関数は、スレッドセーフとして登録されているかどうかにかかわらず、xlcSaveなどのC APIコマンドを呼び出すことはできません。
スレッド セーフとして宣言された XLL 関数が XLM 情報関数を呼び出せないことや、計算されていないセルを参照できないことから、Excel は マクロ シートと同等として登録した XLL 関数をスレッド セーフとしても登録することを許可しません。 そのため、xlCoerce を使用して計算されていないセルの参照の値を取得しようとすると、xlretUncalced エラーで失敗します。 XLM 情報関数の呼び出しは、xlretFailed エラーで失敗します。 その他の前述したポイントは、Excel C API に導入されたエラー コード xlretNotThreadSafe で失敗します。
C API 専用のコールバック関数は、すべてスレッド セーフです。
xlCoerce(未計算のセル参照の強制が失敗した場合を除く)
xlFree
xlStack
xlSheetId
xlSheetNm
xlAbort(中断条件をクリアするために使用される場合を除く)
xlGetInst
xlGetHwnd
xlGetBinaryName
xlDefineBinaryName
�B��̗�O�� xlSet ���ł��B�����̏ꍇ�A���̊��̓R�}���h�Ɠ����ł��邽�߁A�ǂ̂悤�ȃ��[�N�V�[�g�������Ăяo���ł��܂���B
XLL ワークシート関数は、スレッド セーフとして Excel に登録できます。 これにより、複数のスレッドで関数を安全かつ同時に呼び出すことができることが Excel に伝えられますが、実際にはこれが当てはまっていることを確認する必要があります。 スレッド セーフとして登録された関数が安全でないと、Excel が不安定になる可能性があります。
XLL 関数をスレッド セーフとして登録する
スレッド セーフな関数を記述するときに、開発者が従う必要のあるルールは次のとおりです。
スレッド セーフではない可能性のある別の DLL 内のリソースを呼び出さない。
C API または COM を通じてスレッド セーフでない呼び出しを行わない。
クリティカル セクションを使用して、複数のスレッドで同時に使用される可能性のあるリソースを保護する。
スレッド固有のストレージにはスレッド ローカル メモリを使用して、関数内の静的変数はスレッド ローカル変数に置き換える。
Excel には、もう 1 つの制限が課せられます。スレッド セーフな関数をマクロ シートと同等なものとして登録することはできません。そのため、XLM 情報関数を呼び出すことや、再計算されていないセルの値を取得することはできません。
メモリの競合
マルチスレッド システムは、2 つの基本的な問題に対処する必要があります。
複数のスレッドが読み書きするメモリの保護方法。
実行中のスレッドに関連付けられてプライベートになるメモリの作成方法とアクセス方法。
Windows オペレーティング システムと Windows ソフトウェア開発キット (SDK) には、これら両方にそれぞれ対応するツールであるクリティカル セッションとスレッド ローカル ストレージ (TLS) API を使用できます。 詳細については、「Excel でのメモリ管理」を参照してください。
最初の問題は、たとえば、2 つのワークシート関数 (または同じ関数の 2 つの同時実行インスタンス) が 1 つの DLL プロジェクト内のグローバル変数にアクセスする (または変更を加える) 必要がある場合に発生します。 そのようなグローバル変数は、グローバルにアクセスできるクラス オブジェクトのインスタンス内に隠されていることがある点に注意が必要です。
2 つ目の問題は、たとえば、ワークシート関数が関数本体のコード内で静的な変数またはオブジェクト宣言している場合に発生します。 C/C++ コンパイラは、すべてのスレッドが使用する 1 つのコピーのみを作成します。 そのため、関数の一方のインスタンスが値を変更しているときに、別のスレッドのもう一方のインスタンスは値が以前に設定された内容だと想定している可能性があるということです。
MTR のサンプル アプリケーション
ワークシート関数をエクスポートする XLL は、Excel のマルチスレッド再計算 (MTR) を利用できます (該当する関数が、スレッド セーフでないアクションを実行する必要がない場合)。 これにより、Excel はブックを可能な限り短時間に再計算できるようになるため、あらゆるアプリケーションにとって望ましい状態になります。
特に、期待される結果を得るために外部プロセスを呼び出すユーザー定義関数 (UDF) を呼び出しているブックの再計算時間には、MTR が非常に大きく影響します。 具体的に、多数の要求を同時に処理できるリモート サーバーを呼び出している UDF と、その関数の呼び出しを多数含んでいるブックについて考えてみましょう。 ブックの再計算がシングル スレッドの場合、それぞれの UDF の呼び出し、つまりリモート サーバーの呼び出しは、後続の呼び出しの前に完了している必要があります。 これでは、一度に多数の呼び出しを処理できるというサーバーの能力が無駄になります。 ブックの再計算がマルチスレッドの場合、Excel は複数の呼び出しを同時に行うことも、間断なく行うこともできます。
Excel がサーバーと同じ数のスレッド (N と呼ぶ) を使用するように構成されていて、ブックの依存関係ツリーのトポロジで許可されている場合、再計算時間の合計がシングル スレッド計算時間の 1/N に近づく可能性があります。 これは、クライアント コンピューター (ブックが実行されている) にプロセッサが 1 つだけある場合でも当てはまります。特に、サーバーへの呼び出しに要する時間が、サーバーによる呼び出しの処理にかかる時間に対して小さい場合です。
追加のスレッドには、それぞれにオペレーティング システムのオーバーヘッドが存在します。 そのため、特定のブックと特定のサーバーおよびクライアント コンピューターについて、何らかの実験で Excel に設定する最適なスレッド数を見つける必要があります。
たとえば、Excel を実行しているシングル プロセッサ コンピューターと、1,000 個のセルを含むブックがあるとします。 UDF が呼び出され、1 つ以上のリモート サーバーが呼び出されます。 1,000 個のセルが互いに依存していないと仮定して、Excel が次の呼び出しを呼び出す前に 1 回の呼び出しが完了するのを待つ必要がないようにします。 (この制約の緩和は、この例に影響を与えずに可能です)。サーバーが 100 個の要求を同時に処理でき、Excel が 100 個のスレッドを使用するように構成されている場合、実行時間は、1 つのスレッドのみが使用される場合の 1/100 分の 1 に短縮できます。 Excel が各スレッドへの呼び出しを割り当てることと、100 スレッドを管理するオペレーティング システムに関連するオーバーヘッドは、実際には、削減がそれほど大きくないことを意味します。 ここでも、サーバーのスケーリングが適切であり、100 個のタスクを同時に処理するように求めることは、個々のタスクの完了時間に大きな影響を与えないという暗黙の前提もあります。
この技法が大きなメリットになる実用的アプリケーションとして、モンテカルロ法など、小さなタスクに分割して複数のサーバーに依頼できる数値集約型のタスクが挙げられます。
Excel Services の考慮事項
Excel Services は、サーバーでの Excel スプレッドシートの読み込み、計算、およびレンダリングをサポートします。 ユーザーは、標準のブラウザ ツールを使用して、スプレッドシートにアクセスして対話操作を実行できるようになります。
Excel Services の UDF は、Microsoft .NET Framework のマネージ コードを使用して作成し、.NET アセンブリを通じて使用可能になります。 XLL は、Excel Services ではサポートされません。 マネージ コード サーバー UDF リソースは、XLL を呼び出してその機能にアクセスできるため、ユーザーはサーバーで読み込まれたブックとクライアントで読み込まれたブックで同じ機能を使用できます。
したがって、この方法で XLL の関数を使用できるようにするには、引数を変換し、ネイティブ データ型から .NET Framework マネージド データ型に値を返し、XLL 関数を呼び出す .NET アセンブリにラップする必要があります。 .NET ラッパーは、アクセス対象の XLL 関数ごとに 1 つのサーバー UDF をエクスポートします。 追加の要件として、この方法で呼び出される XLL 関数はスレッド セーフであることが必要です。 XLL 関数は本来の方法で Excel に登録されていないため、その関数を強制的にスレッド セーフにする方法がサーバーと .NET ラッパーにはありません。 これは、XLL 開発者の責任で保証します。