|
2.6.15 式のコンパイル
f[x_] := x Sin[x]と定義したとする.右辺の式x Sin[x]は,xがどのような値をとっても適用可能な形で格納される.ユーザによりxの値が特定されると,値はx Sin[x]に代入され式の評価が行われる.特定されるxの値は何でもよい.数値でも,リストでも,代数式であってもよい.Mathematicaの内部コードはすべてのオブジェクト型に対応するように作られている.
しかし,すべてのオブジェクトに対応しなければいけないため,処理時間は余計にかかる.そこで,xが例えば,機械精度の数値であることがあらかじめ分かっているなら,多くの処理ステップを踏む必要がなくなるため,潜在的に,x Sin[x]のような式をもっと速く評価することが可能になる.
式で参照される変数がすべて数(もしくは論理変数)とみなせるなら,関数Compileを使うことで式を高速なコードにコンパイルすることができる.Compile[ , , ... , expr]を使うと,式exprはコンパイルされ,「コンパイル済み関数」を得ることができる.式exprを評価させるには,コンパイルされた関数に , , ... を与えその関数を実行する.
コンパイルの結果としてコンパイル済み関数CompiledFunctionと呼ばれるオブジェクトが生成される.そこには,実際のコンパイル済み関数の評価に必要な命令コード(インストラクション)が入っている.インストラクションは,使用されるコンピュータの機械語で書かれているので,高速な計算処理を実現することができる.

式のコンパイル
xが何であっても,x Sin[x]を計算することができる純関数を作り,それをfと呼ぶ.
In[1]:= f = Function[{x}, x Sin[x]]
Out[1]= 
同じものをコンパイルする.コンパイル済み関数をfcと呼ぶ.
In[2]:= fc = Compile[{x}, x Sin[x]]
Out[2]= 
fもfcも同じ答を出すが,引数が数値なら,fcの方が計算が速い.
In[3]:= {f[2.5], fc[2.5]}
Out[3]= 
数値を使う式や論理演算式を繰り返し評価する必要があるときは,コンパイルしておくと効率が上がる.関数Compileを一度呼び出すだけで,通常のMathematica式に比べ高速に処理してくれるコードが手に入る.
x Sin[x]のように,式が単純であればコンパイルしようがしまいが処理速度はあまり違わない.しかし,式が長くなると,最大で20倍程度まで処理速度を上げることができるため,コンパイルするメリットが大きい.
コンパイルして著しい違いが出るのは,単純な(数論的な)関数をたくさん使った式の場合である.複雑な計算,例えば,ベッセル関数(BesselK)や固有値の解析関数(Eigenvalues)を使った計算では,コンパイルしても処理速度はあまり向上しない.これは,計算時間の大半が,コンパイル処理に影響されないMathematica内部のアルゴリズムで費やされるからである.
ルジャンドル(Legendre)多項式の10次項を求めるコンパイル済み関数を作る.EvaluateによってMathematicaはコンパイルする前に多項式を明示的に構築する.
In[4]:= pc = Compile[{x}, Evaluate[LegendreP[10, x]]]
Out[4]= 
引数を0.4としたときの10次の項を計算する.
In[5]:= pc[0.4]
Out[5]= 
組込み関数を使って同じことを計算させる.
In[6]:= LegendreP[10, 0.4]
Out[6]= 
数値的な関数であればコンパイルすることで計算速度を上げられる.しかし,それでも,できるだけ組込み関数を使うようにした方がよい.組込み関数は最適化してあるので,コンパイルされたユーザ定義の関数より速いことが多い.それに,引数の型が何であろうと同一関数で処理することができる.コンパイル済み関数だと,特定精度の数値的な引数にしか対応することができない.
また,組込み関数によっては自らが関数Compileを呼び出してコンパイルを行うものがある.例えば,数値積分の関数NIntegrateは,与えられた積分式を自動的にコンパイルする.同じように,プロット関数PlotとPlot3Dはプロットする式をあらかじめコンパイルできるようになっている.コンパイル機能を利用する組込み関数にはコンパイル指定のオプションCompiledが用意されている.Compiled -> Falseとしておけば,式のコンパイルを禁止にする.

コンパイルで有効な変数の型
コンパイル機能がうまく機能するには,式にある各オブジェクトの型が確定していることが不可欠である.特に指定がなければ,すべての変数は近似実数とみなされる.
別途指定さえすれば,変数が整数や複素数でも,真偽2値の論理変数でも,さらに,数値配列的なものでもコンパイルをすることが可能である.変数の型を指定するにはパターンを使う.例えば,整数(Integer)なら,_Integerとパターンを指定する.また,論理値の型なら,True | Falseとパターンを指定する.
式5 i + jをコンパイルする.iとjは整数であると指定しておく.
In[7]:= Compile[{{i, _Integer}, {j, _Integer}}, 5 i + j]
Out[7]= 
答は整数型で返ってくる.
In[8]:= %[8, 7]
Out[8]= 
整数型の行列についてリスト操作を行うための式をコンパイルする.
In[9]:= Compile[{{m, _Integer, 2}}, Apply[Plus, Flatten[m]]]
Out[9]= 
生成したコンパイルコードを使いリスト操作を実行する.結果は整数で返される.
In[10]:= %[{{1, 2, 3}, {7, 8, 9}}]
Out[10]= 
Compileの扱える変数の型は,基本的に,コンピュータが機械語レベルで直接処理できる型なら何でもよい.つまり,機械精度の近似実数はコンパイルできる.しかし,任意桁精度の実数はコンパイルできない.さらに,整数に関しては,機械精度の整数,例えば, 以内の整数をコンパイルすることができる.
コンパイルする式が四則演算や論理演算だけのものなら,入力される変数の型さえ判明していれば演算の各ステップで生成される値はどの型のものか自動判定できる.しかし,他の関数を参照したりすると,返される値がどんな型のものか判断がつかなくなってしまう.そのようなときは,特に指定がなければ,戻り値はすべて近似実数の型とみなされる.ただし,特別にリストで型のパターンを与えておけば,式を任意の型のものとして解釈させることも可能である.
引数に整数を与えると,答も整数で返ってくる.
In[11]:= com[i_] := Binomial[2i, i]
x^com[i]の式をコンパイルする.パターンcom[_]にマッチするものは整数であると指定しておく.
In[12]:= Compile[{x, {i, _Integer}}, x^com[i], {{com[_], _Integer}}]
Out[12]= 
コンパイル済みの関数を使い計算してみる.
In[13]:= %[5.6, 1]
Out[13]= 
コンパイルとは,引数の型に応じて最適化された関数を生成することにある.実は,Compileは,それが生成する関数がどんな型の引数を取ろうが正常に機能するようになっている.引数が最適化の対象以外の型なら,コンパイルした式ではなく,標準形の式が呼び出され,引数の型に応じた計算が行えるようになっている.
変数の平方根を計算するコンパイル済み関数を作っておく.
In[14]:= sq = Compile[{x}, Sqrt[x]]
Out[14]= 
引数が実数値なので,最適化済みコードが使われる.
In[15]:= sq[4.5]
Out[15]= 
未知数を与えると,もとの標準形の式が使われる.
In[16]:= sq[1 + u]

Out[16]= 
的確なコンパイルが行われるには,与える引数の型だけでなく,実行時に生じるオブジェクトすべてについて型が判明していなければならない.オブジェクトによっては,型が実行時の引数の型に依存し特定できない.例えば,Sqrt[x]として平方根を計算すると,xが正の値ならば,平方根は実数になる.しかし,xが負の値のときは複素数になってしまう.
コンパイルした関数は常に同じ型の値を返さなければいけない.もし,コンパイルしたコードを実行して,実際に返ってくる型が前提とした型と違うときは,このコードによる計算は無効にされ,代りに標準式による計算が行われる.
この場合,最適化したコードは使われない.もとの標準式が使われる.
In[17]:= sq[-4.5]

Out[17]= 
Compileの重要な機能の1つだが,数学的な式だけでなくプログラム的なものもコンパイルすることができる.例えば,条件式や制御フローの構造体もコンパイル可能である.
コンパイルするものが何であれ,コンパイル関数Compile[vars, expr]に与えた引数は評価されることなくコンパイル処理に直接回される.したがって,式の代りにプログラムもコンパイルすることができる.
ニュートン近似法に基づいた平方根の計算プログラムをコンパイルする.
In[18]:= newt = Compile[ {x, {n, _Integer}}, Module[{t}, t = x; Do[t = (t + x/t)/2, {n}]; t] ]
Out[18]= 
実行してみる.
In[19]:= newt[2.4, 6]
Out[19]= 
|