効率

このセクションでは,コンパイルされた関数を効率よく実行する方法について説明する.高速化のための機能や高速化を阻む問題点等が扱われる.

コンパイルのターゲット

CompilationTargetオプションをCコードに設定してあるなら,コンパイルされた関数のためのCコードとWolframライブラリが生成され,コンパイルされた関数が使われるときにこれも使われる.

次の例は,Cコードが従来のWolfram言語仮想マシンと比べていかに速く実行されるかを示している.

コンパイルされた関数が外部呼出しを行う場合,コンパイルされた関数はCコードと同じ結果を生成するが,速さは同じとは言えないかもしれない.

CのCompilationTargetを使うためにはCコンパイラが必要である.

CのCompilationTargetを並列操作と組み合せることができる.

より詳しくはコンパイルのターゲットのセクションを参照のこと.

並列コンパイル

マルチコアのマシンを使用しているなら,リストの引数に並列に縫い込むコンパイルされた関数を実行することで高速化できる.

次で,連続して実行されるコンパイルされた関数を作り実行する.

次は,同様の,並列実行できるコンパイルされた関数を作る.

より詳しくは並列操作のセクションを参照のこと.

CompilationTarget

CompilationTargetをCに設定することで並列操作を組み合せることができる.

これはWolfram言語関数を計算する最速の方法の一つである.

外部呼出し

Wolframシステムコンパイラはサポートされているシステムタイプをカバーする幅広いWolfram言語コマンドと関数に対する命令を作ることができる.しかし,コンパイラは組込みのサポートがないものに至ることがある.その時点でコンパイラはWolfram言語の評価子を呼び出す.

このことによってコンパイルされた関数の実行速度が落ちることがあるので,このプロセスを理解することは重要である.次は外部関数を定義してこれをコンパイラから呼び出す.

結果は正しいが,こうすると,すべてをコンパイラ内で行った場合に比べて遅くなる.

Compile::noinfoメッセージを有効にすることでこれらの外部呼出しを検知することができる.

このメッセージが表示されたらその原因について考えすべてがコンパイラ内で行われるように関数を書き換えるべきである.

より詳しくは外部呼出しのセクションを参照のこと.

ランタイムオプション

RuntimeOptionsCompileのオプションでコンパイルされた関数のランタイムの設定を指定する.これは,オーバーフローランタイムエラーを制御の仕方やメッセージを出すかどうか等に関する多くの設定を取る.全体的な設定をスピードまたは品質に沿って最適化することができる.

これらのオプションのデフォルト設定を使うと関数は適度な速度で実行される.しかし,デフォルト値は機械整数のオーバーフローを捕らえるようになっている.オーバーフローを捕らえないと重大なエラーが起りかねないのでこれは一般的に見れば好ましいことである.しかし,整数演算のオーバーフローを起さない自信がある場合は この設定が役に立つかもしれない.

次の例はさまざまな整数演算操作を行う.

これで整数のオーバーフローのチェックが無効になり,計算速度が上がった.

より詳しくはランタイムオプションのセクションを参照のこと.

ランタイムエラー操作

コンパイルされた関数が操作中にランタイムエラーに遭遇すると,デフォルトでメッセージが生成され計算はWolframシステムコンパイラ外で行われる.コンパイラ外で計算を実行すると,拡張精度のようなWolfram言語の機能を使って結果が生成される.しかし,このような場合に結果を欲しない場合は,計算時間を掛けずにおきたいかもしれない.

次の例では,オーバーフローが返される.このため計算が繰り返される.

独自のエラーハンドラをインストールしてこの動作を変えることができる.

これで,エラーが起っても評価が繰り返されなくなる.

より詳しくはランタイムオプションのセクションを参照のこと.

コード検査

コンパイラの命令を調べるとどのように動いているかがある程度分かる.CompiledFunctionTools`パッケージを使って調べるができる.使用する前にパッケージをロードする必要がある.

これで,CompilePrintを使ってコンパイルされた関数の詳細が表示できるようになった.

例題

ニュートン法フラクタル

古典的なフラクタルは,原始根に適用されるニュートン法の複素面のbasin of attractionで作られたパターンである.以下の例は,CompileとWolfram言語の普遍性をいかに簡単に使って,インタラクティブに根を動かすとフラクタルがどのように変化するかを見ることができるかということを示すものである.

多項式 を定義する 個の根 の集合を使って,ニュートン法は非常に簡単な形を取る.ステップは で与えられる.

basin of attractionを表示するために,複素面の各点について,ニュートン法がどの根に収束するか,そして収束する場合にはいくつの反復で収束するかを求める必要がある.この情報は,以下のように色空間にマップすることができる.根 i が色Hue[i/n]で表されるとする.反復を少なくするには,色がよりパステルカラーになるように飽和を大きくする.反復を増やすには,輝度を減らして色を暗くする.

以下のセルで,複素点 z0 に根の任意集合のニュートン法を適用する関数を定義して,maxiter で制約されながらどの根に収束し,いくつの反復で収束するかを見極める.単純な収束テストに$MachineEpsilonの値(これは変更することができるが,変更されるべきではない)を得るのにWolfram言語評価を使わなくて済むように,オプション"InlineExternalDefinitions"->Trueが使われた.この関数は,1つのプロットを作成するのに何千回も実行され,その実行の速さが重要になってくるので,RuntimeOptions->"Speed"およびCompilationTarget->"C"のオプションが使われる.

以下では,任意数の根nについて基本的な色のRGB値のリストを返す関数を定義する.Wolframシステムコンパイラは直接色の値をサポートしないので,リストが必要である:

例として,根が の根であるとする:

Tableを使って複素面の点の集合を作り,Mapを使って関数を(レベル2で)適用することによって,この関数を使うことができる.Rasterを使うとデータを非常に速く表示することができる:

これではまだインタラクティブ機能を使うには遅すぎる.大きくスピードを上げる方法には,生成されたCコードからロードされたLibraryFunctionへの効率化されたアクセスとしてCompiledFunctionを使う方法がある.プロットは点の行列に縫い込むことによって作成されるので,これを行う自然な方法として,RuntimeAttributes->Listableを使う方法がある.この方法は,その後 Parallelization->Trueと設定して,別のニュートン反復を並列で実行することができるという点でも便利である.newt の現行定義ができるだけ効率的に使われるように,オプション "InlineExternalDefinitions"->Trueを使うことが重要である.

これを上と同じ点集合に使う:

複数のコアがなくても,スピードがかなり改善され,インタラクティブ機能が問題なく使えるはずである.

以下はラスタ,xの方向にnx個の点,そしてyの方向にny個の点を持つ範囲上の根のフラクタルを作成する関数を定義する:

以下で関数を使ってみる.以下のインタラクティブな例では4分の1秒より短い時間しかかからないので, できるだけ大きいmの値を求めることが望ましい:

Manipulateを使い,sroots から始めて根の位置を動かすことができる関数を設定する.Manipulateが意図される引数を明示的に得られるように,Functionを使う.これは,Manipulateが自身のスコーピングを行い,いくつかの記号について自身のインスタンスを使うことがあるので,必要である:

以下のコマンドの入力には,実線上の根から始めて関数を使う:

ジュリア集合

以下の例では,作動するけれどもインタラクティブにするにはゆっくりすぎるコードを,Manipulate内でうまく実行できるコードにする方法について説明する.

以下のコードは,さまざまな種類のフラクタルを生成することができるWolfram言語コードが掲載されているWebページhttp://www.bugman123.com/Fractals/index.htmlから取られたコードである.

いくつかのステップを通して,どのようにしたら,より多くの点を数倍の速さで生成するように,このコードを最適化することができるかについて見てみよう.

まず,多くの数の点を累算するのには時間がかかりすぎるので,Appendを繰り返して使用することを止める.その代りの方法として最適なのは,累積を効率的に扱うNestWhileListを使う方法である.

これで同じ結果が少し速く計算される.より長いリストでは,Appendを使うことによるコストは二次的に増える.余分な最初の値を削除することは可能であるが,それでもプロットに大きく影響を与えることはなく,下に示す最も速い方法よりも少し遅い.

次に,変数を局所化し,可能な場合には機械複素数か実数の定数を使う.型を一貫したものにするために行われることの一つに,{1,-(-1)^(1/3),(-1)^(2/3)}Table[Exp[-2. N[Pi]I k/power],{k,0,power-1}](これは3以外のベキに一般化する)で置き換えることがある.これを変数に入れ,1度だけ計算することによって時間が短縮できるimaxdzmax のような定数は,Moduleの設定に含み,後で変更しやすいようにする.

以下はさらにもう少し速い.しかし,適切な型を使っているので,本当の意味でスピードを上げるにはこれをコンパイルする必要がある.

この方法の方がずっと速いので,パラメータを増やして,集合のより多くが計算されるようにすることができる.これを行う場合は,点の合計数に上限を加え,プロット作成に時間がかかりすぎたり,メモリの問題が起ったりしないようにする.その他の変更点として,複素数の値の代りに座標を生成するようにする.座標はプロットで使用されるので,上のように余分なMapを使う代りに,一つのCompiledFunctionですべてをまとめて行い,時間を短縮することができる.

プロットは上のものよりも詳しいものとなり,しかも生成にかかる時間がかなり削減できる.

座標軸を削除し,直接点をプロットすることによって,時間が大きく削減できることもある.

できるだけ速くするために,可能な設定の中で最も速い設定を使ってCコードにコンパイルする.

Cコンパイラがある場合には,これでManipulateの中で使って,優れたインタラクティブ機能を得るのに十分な速さになったはずである.以下の入力を使ってパラメータをインタラクティブに変更することができる.Cコンパイラがない場合には,「個々のコンパイラ」に入手してCCompilerDriverパッケージで使えるコンパイラがリストされている.