コンパイルされた関数の操作
Wolframシステムコンパイラは,Wolfram言語計算を評価するための一連の簡単な命令を含むCompiledFunction式を生成する.コンパイルされた関数の式には,引数と結果の指定,フラッグ,エラーハンドラ,バージョン情報等の他の情報も含まれる.この情報のすべてが式に保存されるので,コンパイルされた関数を使うためのツールの多くは,Wolfram言語を使って書くことができる.
コンパイルされた関数の命令は,Wolfram言語に含まれる仮想マシンによって実行することができる.これは,一時的な場所を使って(登録して)計算の中間結果を保存する理想レジスタマシンに基づく.
命令をどのように使えばよいかについては,CompiledFunctionTools`パッケージを使って学ぶことができる.パッケージを使うには,まずこれをロードする必要がある.
CompilePrintを使って,コンパイルされた関数の詳細を表示させることができる.
この計算で,コンパイルされた関数は実数の引数を1つ取り,実数の引数を返すことが分かる.引数はレジスタR0に置かれ,結果はレジスタR3の中に見られる.引数の平方がレジスタR1に保存され,これは2度使われる.さらにコンパイルされた関数のランタイム設定についての他の情報が表示される.
この例は,コンパイラの主要部分について示すものである.コンパイラは型のシステムを管理する.入力の型が指定され,それぞれの中間結果の型が計算されてから,結果の型が最後に示される.
引数の型は入力で指定することができる.例えば,入力が複素数であることを指定するには,以下のようにComplexを使用する.
この例では,平方や正弦を求める等の操作に複素数のバージョンが使われる.これによって,実際の引数に基づいて関数が呼び出されるたびにどうするかを決定する代りに,複素数用に最適化された関数が使われるため,操作を高速化することができる利点がある.
コンパイラは,ベクトル,行列,テンソルを使うこともサポートする.以下はその簡単な例である.
コンパイラは,Table,Map, Apply,Nest,NestList,Fold,FoldList等数多くのWolfram言語のプログラミングコンストラクトをサポートする.以下の例では,どのように局所変数をModuleで紹介してから,インタラクションをDoで実行することができるかを示す.
このようなコンパイルされた関数は,コンパイルされていないものよりもずっと速く実行されることが多い.
型のシステム
Wolframシステムコンパイラは,型のシステムを使ってその命令を生成する.それぞれの中間計算の型は,関数の知識とその引数の型から決定される.型は関数の入力引数について指定することができる.また,局所変数を定数に割り当てることによって,任意の型の変数を紹介することもできる.
Wolframシステムコンパイラで使用することができる型は,以下のセクションにまとめられている.
ブール型
ブール型は,コンパイルされた関数に引数として渡すことができ,局所変数の値としてもサポートされる.
整数
整数は,コンパイルされた関数に引数として渡すことができ,局所変数の値としてもサポートされる.
実数
実数は,コンパイルされた関数に引数として渡すことができ,局所変数の値としてもサポートされる.
複素数
複素数は,コンパイルされた関数に引数として渡すことができ,局所変数の値としてもサポートされる.
テンソル
コンパイラは,ベクトル,行列,テンソルで使える.これらは,コンパイルされた関数に引数として渡すことができ,局所変数の値としてもサポートされる.
関数を整数のベクトルで呼び出すと,これは実数のベクトルに変換される.
まとめ
Compile[{x1,x2,…},expr] | xi の数値について expr を評価するコンパイルされた関数を作成する |
Compile[{{x1,t1},{x2,t2},…},expr] | xi が型 ti であることを仮定して expr をコンパイルする |
Compile[{{x1,t1,n1},{x2,t2,n2},…},expr] | xi はそれぞれが型 ti であるオブジェクトの階数 ni の配列であると仮定して,expr をコンパイルする |
_Integer | 機械サイズの整数 |
_Real | 機械精度の近似実数 |
_Complex | 機械精度の近似複素数 |
TrueFalse | 理論変数 |
Compileの型指定.
型の伝播
コンパイルされた関数の引数を指定した後,コンパイラの関数群を通して型を伝播する.通常この操作は比較的簡単である.例えば,実数が2つ追加されると,結果は実数になる.
CompiledFunctionTools`パッケージはコンパイラの操作を示すのに便利である.まずこれをロードしなければならない.
CompilePrintを使ってコンパイルされた関数の詳細を示す.以下では,複素数の平方根も複素数であることを示す.
しかし,入力が実数である場合には,平方根は複素数であることも,実数であることもある.以下はこれを示す例である.
上の例では,この入力についてコンパイラが平方根の結果にも実数を選んだことを示している.これは一般的ではないが,より速く結果が出される.
型の強制
Wolfram言語の数値強制システムは,結果を最も正しい形で維持する.例えばは,それ以外の結果では精度が失われるので,それ自身を評価で返す.
これは,他の多くの計算システムとは異なる点である.例えば,CあるいはFortranのプログラムは結果を浮動小数点数に変換する.これは,CあるいはFortranでは2の平方根を計算した結果を表すのにそれ以外の方法がないからである.Wolfram言語では記号的機能によって等の式を表示したり使用したりすることができるため,このようなことは起らず,浮動小数点数に変換することを避けることによって情報の損失を避けることができる.
しかし,が浮動小数点数と組み合せられた場合には,Wolfram言語は浮動小数点数を返す.
同じような問題が厳密な入力においても起る.厳密な入力には,整数,有理数,PiやE等の定数が含まれる.
コンパイラはCompileから直接起動されると,異なる型の強制を返す.例えば,平方根のような関数内部で使用された場合に,整数を実数に変換する.次の例では,結果は実数のベクトルである.
コンパイラは,厳密な数学ではなく数値計算を最適化することに重きを置くので,このような異なった方法で作動する.
しかし,コンパイラが自動的に呼び出される場合,例えばMapのような関数へのトップレベルの呼出しによる場合には,コンパイラはその強制が通常のWolfram言語での強制と同じになるモードで実行される.コンパイラが適切な結果を返せない場合には,コンパイラは使われない.
このことによって,Wolfram言語は内部の最適化としてコンパイラを使うことはできるが,コンパイラが使われなかった場合と同じ結果を維持することができる.次の例では,結果は厳密な整数と整数の根のベクトルである.(コンパイラが使用されなければ,結果は実数のベクトルになっていたはずである.)
型の一貫性
コンパイラの型のシステムが計算における中間結果の型を決定する.これらの結果は,一貫した方法に従う必要がある.例えば,分岐がある場合には,分岐の両側は同じ結果を返さなくてはならない.以下では,分岐が違っていて互換性がないため,コンパイルエラーが返される.
型の強制は,一貫した型に移動するために適用することができる.以下はこれを示す.
分岐間の型の一貫性に大きく関係している問題に,return文の型の一貫性がある.次の例は,関数から戻り値まですべてにおいて型が一貫していなければならないことを示す.
型の一貫性のもう一つの例に,局所変数に関する例がある.一旦局所変数が型に割り当てられると,それを互換性のない型の値に割り当てることはできない.
外部呼出し
Wolframシステムコンパイラは,サポートされる型システムを含むさまざまな種類のWolfram言語のコマンドと関数用に命令を作成することができる.しかし,時にはコンパイラが組込みサポートを持たないものに連絡しようとする場合もある.
このような場合でも,コンパイラは継続することができる.結果を計算するための命令を作成しなければならなく,これはWolfram言語の評価器に呼出しをかける命令を加えることによって行うことができる.しかしこれに加えて,コンパイラは結果の型を決定することも行わなければならない.この情報が追加されていない限り,これは容易なことではない.
この過程はしばしばコンパイラの非効率性の大きな原因となるものであるので,これについて理解することは大切である.
CompiledFunctionTools`パッケージは,コンパイラの操作を示す場合に便利である.まずこれをロードしなければならない.
以下は,コンパイルされた関数の内部から呼び出される外部関数を示している.命令はWolfram言語の評価器が呼び出され,結果は実数になることが予想されることを示す.
以下では,異なる定義を持つ外部関数が使われている.コンパイラでは実数の結果をまだ期待していることが示されているが,実際にはブール型が返される.
これらの問題は,外部関数がブール型を返すことを宣言することによって避けることができる.
未定義の外部呼出し
コンパイラは,情報がない入力部分について外部呼出しを行う.このことは,上で示したように問題を引き起すこともある.これらの問題は,コンパイルされた関数が実行される場合に効率性の問題を確実に引き起す.問題がない場合でも,コンパイルされた関数の計算は非効率的であることがある.
これらの問題を追跡する1つの方法にCompile::noinfoメッセージを使う方法がある.これは,コンパイル処理の詳細を示すために有効にすることができるが,デフォルトでは無効になっている.
Onでメッセージを有効にすることができる.
これでコンパイルするとメッセージが生成されるようになった.これは効率性の問題をチェックするために便利な方法である.
パックアレー
パックアレーは,Wolfram言語において重要なスピードとメモリを向上させる機能である.パックアレーは,Wolfram言語が記号計算をサポートできるように一般的で,効率的な数値計算を行うように高速であるようにするために存在するものである.整数,実数,複素実数等の機械数のベクトル,行列,テンソルの特別な表現を返す.
バイト数が測定されると,ベクトルはより多くのメモリを使用することが分かる.
コンパイラは内部的にパックアレーを使うので,パックアレーはコンパイラにとって大切なものである.パックアレーはコンパイラに素早く渡し,コンパイラから素早く戻るので,効率性が向上する.
結果はパックアレーである.コンパイラがベクトル,行列,テンソルのいずれかを返すたびに,コンパイラは必ずパックされる.
コンパイルエラー
Wolframシステムのコンパイラは,Wolfram言語式を評価するための一連の命令を生成する.コンパイラが式の部分を通っていく際に,以下のような問題に遭遇する場合がある.
このような問題が問題なのは,Wolfram言語は記号計算を行うため,認識されない入力が実際には意味のある有効な式である可能性があるということである.
コンパイラが取る解決策は,このような問題の多くを処理する外部呼出しを使って、計算がコンパイラで実行され続けるようにするという方法である.それでも警告メッセージが出されることもある.
CompiledFunctionTools`パッケージはコンパイラの操作を示す上で便利である.まずこれをロードする必要がある.
以下では,コンパイルされた関数の内部で未知の関数が呼び出されたことを示す.結果では実数が予期されているが,もしそうではない場合には,ランタイムエラーが起る.コンパイル中にはエラーメッセージは出されない.
以下は2つの引数を持つSinを呼び出す.この場合,エラーが起り,外部呼出しが行われる.これは,Wolfram言語の記号計算の法則全体で一貫して行われる.
以下ではブール型の引数を持つSinを呼び出す.この場合は,エラーが起り,外部呼出しが行われる.これは,Wolfram言語の記号計算の法則全体で一貫して行われる.
これは型が矛盾している例である.コンパイラはエラーメッセージを生成する.
以下は,条件文の1つの分岐では変数が初期化されているが,もう一つの分岐ではされていない変数エラーの例である.コンパイラはエラーメッセージを生成する.
ランタイムエラー
Wolfram言語の仮想マシンは,コンパイルされた関数を特定の入力引数について実行する.終了すると結果を返す.しかし,マシンの実行中にエラーが生じることがある.以下は起りうるエラーの例である.
これらのエラーはすべてコンパイルされた関数の実行速度に影響を与える.これはエラー自体による場合と,エラーに対処するために余分に加えられたコードによる場合がある.
ランタイムエラーが起る場合,コンパイルされた関数の実行は終了され,エラーハンドラが呼び出される.通常これは,Wolfram言語インタプリタで計算が繰り返されるように設定される.
数学関数のエラー
数学関数のエラーは,領域問題か関数の例外から起ることがある.
領域問題はコンパイラの型のシステムにより起る.例えば,実数を使う場合に,入力によってはエラーを引き起す場合がある.次の例では,コンパイルされた関数は正の入力に使える.
しかし,入力が負であると,ランタイムエラーが生成される.この時点でエラーハンドラが呼び出され,Wolfram言語インタプリタに切り替えられる.
これらのエラーはWolfram言語インタプリタでは類似のものを持たない.インタプリタはランタイムに領域間で切り替わるからである.つまり,1つの型,Wolfram言語式だけが存在する.
コンパイラでは,複素数を使うことによって,このようなエラーを避けることができる.しかし,そうすると速度に影響が及ぶ.
関数の例外は,数学関数の入力の一部で起る.次の例では,ベキが問題なく計算される.
しかし,00は未定義であり,これによってランタイムエラーが生成され,Wolfram言語インタプリタが呼び出される.するとインタプリタで計算の問題が起り,Indeterminateが返される.
オーバーフロー
数字がその動的な範囲を超える場合にオーバーフローが起る.Wolfram言語では,これはマシンハードウェアで実装された数字からソフトウェアで実装された数字へ自動的に切り替えることによって処理される.この切替えによって,Wolfram言語は有益な結果を返し続けることができるが,ソフトウェア演算(Wolfram言語では非常に効率的ではあるが)はハードウェア演算よりもゆっくりであるため,効率性の問題が起る.
しかし,結果は機械整数ではない.(これは32ビットの符号付き整数を使って行われている.)
この計算がCプログラムで行われたとすると,結果は-2147483648であったはずである.これは,整数演算は整数を実装するのに使われるビットパターンで使えるように定義されるからである.(整数は多くの場合実際の操作を行う.)結果として,演算は速く行われるが,必ずしも「数学的に」正しい結果は出ない.もちろんプログラマーはこれらのエラーについて認識しているため,問題が起りそうな場合にはそれを見付けるように気を付ける.
浮動小数点数の計算では,数字は次の機械実数で示すようにオーバーフローになることがある.
しかし,結果は機械実数ではない.(これは倍精度実数を使って行われている.)
数字が機械実数で与えられる範囲よりも小さくなったときにも同じことが起る.
オーバーフローを含む計算がCにおいて機械実数を使って行われる場合,結果は機械整数の場合とは若干違ったものになる.これは,機械実数が演算の例外を許す標準(IEEE 754)に従うからである.オーバーフローについては,結果は通常機械実数の無限大になる.この機械実数の無限大は,標準で定義される方法で次の計算に伝播する.
このオーバーフローについての簡単な記述からも,なぜWolfram言語が便利であるかが分かる.Wolfram言語はこのような詳細をすべて処理してくれる.もちろんCあるいはFortranのプログラマーの中にはこのような問題を無視して,計算がこのような問題に遭わないことを期待する人もいる.
Wolfram言語でコンパイルされた関数の実行についてもこの問題に遭遇する.通常コンパイルされた関数は整数問題を見付ける.以下ではこれを示す.
しかし,整数のオーバーフローをチェックするには,通常非常に速い操作に余分な作業を強いることになる.このため速度の低下が起ることもある.RuntimeOptionsを設定することによって,このチェック機能を無効にすることもできる.以下では,整数オーバーフローのチェック機能が無効にされ,数学的に誤った答が返される(但し、計算は速い)例を示す.
しかし,実数計算のオーバーフローエラーへの対処は整数計算の場合と異なる.実際,エラーは計算の最後で検知される.このため,オーバーフローの結果を返さない関数が使用されると,エラーは生成されない.
これは,ハードウェア演算ではこれらのエラーが別の計算に伝播することを許すので,実数計算で可能である.利点は,計算を途中でチェックする必要がないので,計算性能が向上することである.
実数のエラーが起っている最中にこれをチェックすることができるRuntimeOptionsを設定することもできる.
外部呼出しのエラー
コンパイルされた関数の実行がWolfram言語インタプリタを呼び出すが,結果は予期される型ではない場合に,外部呼出しエラーが起る.以下の例では外部呼出しがどのように紹介されるかを示す.
以下では,コンパイルされた関数が作成され,これは外部評価の予想される結果を持つ.
ランタイムエラーは起らないが,外部呼出しが非効率的であることに変わりはないので,できるだけ避けるのが得策である.
リスト処理とその他のエラー
コンパイルされた関数の実行中には,数多くの別の種類のエラーが起り得る.以下のコンパイルされた関数はベクトルの要素を返す.
部分の数がベクトルの長さよりも大きい場合には,エラーが起る.
ランタイム属性
属性は,Wolfram言語関数のさまざまな特性を指定する方法である.Listableは便利な属性の一つであり,リスト可能な関数は自動的にその入力を任意のリストに縫い込む.
Functionの属性を指定することもできる.
多くの組込み関数がListableの属性を持つ.
CompileのRuntimeAttributesオプションを設定することによって,コンパイルされた関数に属性を与えることができる.現在Listableの属性のみ設定することが可能である.以下でそれを示す.
リスト可能なコンパイルされた関数は,1つの数字に通常の方法で使える.
しかし,引数に入力指定よりも高い階数を持つリストが含まれると,関数はその引数に縫い込む.
リスト可能なコンパイルされた関数は,大量のデータを使うが,その中に分岐やテストが含まれる関数を作成するのに便利である.例えば,以下の関数はリスト可能である.
コンパイルされたバージョンの関数が作成される.この方がずっと速く実行できる.
コンパイルオプション
CompilationOptionsは,コンパイル過程の設定を指定するCompileのオプションである.現在唯一設定できるのは,式の最適化レベルである.
CompilationOptionsで個々の設定を作成することができる.
"ExpressionOptimization" | Automatic | 入力式を最適化するかどうか |
"InlineCompiledFunctions" | Automatic | ネストされコンパイルされた関数のボディを展開するかどうか |
"InlineExternalDefinitions" | Automatic | 外部定義を使うかどうか |
ExpressionOptimization
"ExpressionOptimization"オプションは,Compileの入力に最適化が適用されるべきかどうかを制御する.
デフォルト設定では,入力設定は同じものを2回以上計算することを避けるために最適化される.以下では,どのようにx2が1度だけ計算されて,局所変数と一緒に保存されるかを示す.
式の最適化をFalseの設定を使って無効にすることができる.
デフォルトはAutomaticの設定である.これは,コンパイルされた関数が外部呼出しを行わなくてはならない場合には,最適化が行われないことを意味する.
Trueの設定は,外部呼出しがあっても最適化を強制する.
InlineCompiledFunctions
"InlineCompiledFunctions"オプションは,ネストされコンパイルされた関数がランタイムに埋め込まれるか,それとも呼び出されるかを制御する.
以下の簡単なコンパイルされた関数は別の関数から呼び出すことができる.
このコンパイルされた関数は簡単な関数を呼び出す.これはWithを使って簡単なコンパイルされた関数をそのボディに置く.
CompiledFunctionTools`パッケージをロードする.
これでCompilePrintは,簡単な関数がどのようにコンパイルされた関数に埋め込まれたかを示すようになった.
"InlineCompiledFunctions"をFalseに設定すると,ネストされた関数は埋め込まれない.代りにこれは,Wolfram言語評価器を使用しないという特別の命令と一緒に呼び出される.
"InlineCompiledFunctions"のデフォルト設定はAutomaticであり,小さな関数を埋め込むのに発見的問題解決を使う.
コンパイルされた関数を直接呼出しを行うのは不便である.これはCompileが属性HoldAllを持つため,その引数を評価しないからである.以下の例では,Withを使って呼び出された関数を呼出し側のボディに挿入することによって,この問題を避けている.これは若干ぎこちないやり方であり,関数の再帰的操作を妨げるものである.
もう一つの方法に,定義を使って,呼び出されたコンパイルされた関数をホールドする方法がある.しかしこれは,"InlineExternalDefinitions"オプションが設定された場合にだけ使える方法である.
以下では,"InlineExternalDefinitions"のデフォルト値が使われている.これは外部定義を挿入せず,コンパイルされた関数は埋め込まれない.コンパイルされた関数はコンパイラを呼び出すのに特別の命令を使わない.
"InlineExternalDefinitions"がTrueに設定されている場合には,変数cfの定義が使われる.以下はコンパイルされた関数を挿入する.その後"InlineCompiledFunctions"が使われ,これによって関数が埋め込まれる.
以下では外部定義が使われているが,コンパイルされた関数は埋め込まれていない.代りにコンパイルされた関数が別のコンパイルされた関数を呼び出すことを許す効率的な命令を使う.このような種類の呼出しは,コンパイルされた関数が自身を呼び出すことを可能にするので,また並列実行がコンパイラで行われるときに,同期ロッキングなしに呼出しを行うことができるので,重要である.
以下では,再帰呼出しが設定されている.コンパイラはそれでも効率的な命令を使ってネストされた呼出しを行う.
InlineExternalDefinitions
"InlineExternalDefinitions"オプションは,外部定義がコンパイルされた関数に埋め込まれるべきかどうかを制御する.
以下は外部定義を作成し,これは記号valにホールドされる.これはCompileによって使われる.
一般化されたコンパイルされた関数が何を行うかについて理解するには,CompiledFunctionTools`パッケージを使うと便利である.まずこれをロードする.
これでコンパイルされた関数がどのように作動するかが見られるようになった.コンパイルされた関数は関数定義に外部呼出しを行うが,正しい型を得るために値を処理する.これが"InlineExternalDefinitions"のデフォルト設定であるAutomaticを使った結果である.
"InlineExternalDefinitions"をTrueに設定すると,外部定義はコンパイルされた関数に挿入される.定義が変更されると,コンパイルされた関数は引き続き古い値を使うので,これはデフォルトでは行われない.
"InlineExternalDefinitions"がFalseに設定されると,外部定義はコンパイルされた関数に挿入されず,型の情報は定義から導き出されない.外部評価の結果は,整数であるべきであるが,実数になる.
"InlineExternalDefinitions"オプションは"InlineCompiledFunctions"オプションと一緒に使うことができる.以下の例では,別のコンパイルされた関数に呼出しが行われる."InlineCompiledFunctions"をFalseに設定することで,コンパイルされた関数が埋め込まれることは防げるが,コンパイルされた関数から別のコンパイルされた関数への呼出しに効率的な命令が使われるようになる.
ランタイムオプション
RuntimeOptionsは,コンパイルされた関数のランタイム設定を指定するCompileのオプションである.これは,オーバーフローとランタイムエラーがどのように処理されるべきかを制御するための数多くの設定を取る.全般的な設定を与えることで,スピードあるいは質を最適化させることができる.
以下はデフォルトの設定を示す.デフォルト設定は,機械整数のオーバーフローを捕らえる.計算はその後Wolfram言語インタプリタを使って繰り返される.
ランタイムオプションを"Speed"に設定すると,機械整数のオーバーフローはチェックされず,計算は誤った結果を出す.機械整数のオーバーフローが起らないことが分かっている場合には,このオプションを使ってもよい.
RuntimeOptionsにおいて個々の設定を行うことができる.
"CatchMachineOverflow" | False | 実数のオーバーフローが起っているときに捕らえるべきかどうか |
"CatchMachineIntegerOverflow" | True | 整数のオーバーフローが捕らえるべきかどうか |
"CompareWithTolerance" | True | 比較が同じようにSameQに使えるべきかどうか |
"RuntimeErrorHandler" | Evaluate | 関数の実行中に致命的なランタイムエラーが起った場合に適用する関数 |
"WarningMessages" | True | 警告メッセージが省略されるべきかどうか |
CatchMachineOverflow
デフォルトで機械数のオーバーフローは,計算の最中には捕らえられない.このため,以下の例ではランタイムエラーを生成しない.
オーバーフローをチェックする機能をオンにすると,ランタイムエラーが生成される.
コンパイラが機械数のオーバーフローがあるWolfram言語に結果を返すと,エラーが生成される.
CatchMachineIntegerOverflow
デフォルトで機械整数のオーバーフローは捕らえられ,ランタイムエラーが生成される.
機械整数のオーバーフローをチェックする機能をオフにすると,ランタイムエラーは生成されないが,結果は誤ったものであるかもしれない.
RuntimeErrorHandler
"RuntimeErrorHandler"設定は,ランタイムエラーがあるときに使用される.以下の例では,ランタイムエラーが起った場合に,例外を投げる.
エラーがない場合は,コンパイルされた関数は通常通り作動する.
ランタイムエラーがある場合には,特別のランタイムエラーハンドラが呼び出される.
デフォルトのランタイムエラーハンドラは,Wolfram言語インタプリタを使って計算を繰り返すためだけのものである.