線形代数計算のパフォーマンス
パックアレー
Wolfram言語を構築する際の課題のひとつに,システムが一般的で記号計算が可能であり,計算が早くて数値計算が効率的であるということがある.これらの課題と,その他の目標については,「Wolfram言語の設計原理」に述べてある.Wolfram言語におけるひとつの方法に,Wolfram言語式の重要な型の特別な表記方法を用いることが挙げられる.線形代数に関しては,パックアレー技術と呼ばれるものが関わってくる.これはバージョン4.0で初めて導入されたものである.
最初のベクトルが使用するメモリ量の方が少なく,計算が速くなる.これはパックアレーで構築されたベクトルであるからである.パックアレーは機械整数,機械実数,機械実数の実部と虚部を持つ複素数の長方形テンソルを表す方法である.
パックアレー関数
Developer`PackedArrayQ[expr] | expr がパックアレーならTrueを返す |
Developer`ToPackedArray[expr] | 可能ならexpr をパックアレーに変換する |
Developer`ToPackedArray[expr,t] | 可能ならexpr を型t のパックアレーに変換する |
Developer`FromPackedArray[expr] | expr をパックアレーから変換する |
Developer`PackedArrayForm[expr] | expr のパックアレーの概略を表示する |
パックアレー操作
多くの関数は,パックアレーを使うと,パックアレーでないもののリストを使う場合に比べて,より効率的に計算が行われる.しかし,パックアレーでもパックアレーでなくても結果は同じである.結果が同じであるためには,システムは時折パックアレーをアンパックしなければならない.これは効率を悪くする潜在的な原因であり,何かがアンパックされることを伝えるメッセージが用意されている.このメッセージはシステムオプションを設定すると利用できる.
パックアレーのまとめ
パックアレーを使用するのに,Developer`ToPackedArray等の関数を利用するのは通常難しい.重要なのは,使用する行列の要素がすべて一様であるということである.つまり,例えば要素に整数と実数が混在しないようにしなければならないということである.
プログラミング効率
このセクションでは線形代数問題を解く効率的なWolfram言語プログラムの書き方について述べる.
パフォーマンスの計測
Wolfram言語には,コードのパフォーマンスについて学ぶ際に利用できる,計算時間に関する数多くの関数がある.
Timing[expr] | expr を評価し,所要CPU時間と得られた結果のリストを返す |
AbsoluteTiming[expr] | expr を評価し,所要絶対時間を返す |
TimeConstrainedも便利である.
TimeConstrained[expr,t] | expr の評価を試み,t 秒後に計算を放棄する |
TimeConstrained[expr,t,failexpr] | 制限時間で終らなかったらfailexpr を返す |
ループのベクトル化
通常の計算において,行列の要素に対してある操作を繰返し適用するということはよくある.行列は大きいこともあり,その場合は効率が重要になってくる.
効率的なコードを書く重要な方法のひとつに,特に内部ループにおいて明らかな部分参照は避けるということが挙げられる.このような部分参照は効率を劣化させる主因となり,行列やベクトルのすべての要素に一度で作用できる関数を使うことで回避できる.以下のようなベクトル化操作を使うと,Wolfram言語はより速く実行できる,高度に最適化されたコードを使うことができる.
リストの作成
次の問題では,リストを作り,それを特定の値で初期化する.例として,以下の計算を考える.
v〚i〛 ⟹ Sin[i]
リストの初期化のひとつの方法として,リストを作成し,Forループで繰り返し要素に値を割り当てる.
n = 10;
v = Table[0, {n}];
For[i = 1, i <= n, i++, v[[i]] = Sin[2.0 Pi i]];
リストを作成し,一度にリストを初期化できたらずっと速い上に,整然とする.
n = 10;
v = Table[ Sin[2.0 Pi i], {i, 1, n}]
さらに速くするには,最適化された関数Rangeを使ってリストを作成し,ベクトル化操作を行う.
n = 10;
v = Sin[2.0 Pi Range[1., n]]
TableやRange等のコマンドはリスト,行列の構築に非常に最適化されている.これらについては「行列の構築」のセクションで述べた.
リストの更新
次の問題では,リストの要素を更新する.例えば,各要素のSinを計算する.
v〚i〛 ⟹ Sin[v〚i〛]
これを行うひとつの方法は,リストの各要素を繰返し更新していくというものである.
Do[v[[i]] = Sin[v[[i]]], {i, n}];
これはベクトル化操作を用いると速くなる.コードもすっきりする.
v = Sin[v]
引数をもう1つ使う別の方法がある.この引数はリスト全体で定数である訳ではない.
v〚i〛 ⟹ v〚i〛/i^2
これを行う方法のひとつとして,指標の2条で各要素を割るというものがある.
n = Length[v];
Do[ v[[i]] = v[[i]]/i^2 , {i, n}];
更新の計算を行ってからベクトル化した割り算を実行する方が速い.
d=Range[n]^2;
v=v/d
リストへの縫込み操作については「リストへの縫込み」で説明してある.
組込みサポートの使用
Wolfram言語には非常に多岐に渡る関数がある.行列の計算を書く重要な用途を持つリスト操作ができるものもたくさんある.使い始める前に,組込み関数で使えるものがないかどうかを見てみるとよい.例えば,たたみ込みや相関等を行っているなら,それに適した関数を使うと,確実に計算が速くなる.
Wolfram言語が広範な数学オブジェクトを表すことができるということも特筆すべき点である.例えばWolfram言語は行列を使って表されている実際の線形方程式も扱える.方程式の形は時に,より表現力に富んで便利であり,より効率的になることもある.これについては「式の疎配列への変換」に説明してある.
線形代数計算に応用できる組込み関数の分野のひとつに,リストの構造操作がある.Part,Take,Drop,Transpose,PadLeft,PadRight,RotateLeft,RotateRight等のWolfram言語の関数は非常に最適化されており,広く応用できる.詳細は「構造操作」を参照のこと.一般には,これらの関数を使って計算をした方がよい.
ここで,行列のコピーを並べて拡張するさまざまな方法について取り上げる.例として,次の入力行列
遅い方法
この方法はスピードが極めて遅い.その中でも,部分指標に対する内部ループが最も遅くなる.これらはベクトル化したコードと置き換えると速くなる.これについては内部ループで部分参照を行うと,非効率なコードになると「パフォーマンスの計測」に述べられている.
速い方法
この方法は,非常に柔軟で,高度に最適化されている関数Partを使っているため,はるかに速い.詳細は「行列の一部の抽出」に記述してある.
速く,かつ整った方法
この方法はより整っており,入力行列が小さいときは速いことが多い.大きい入力行列については,上記のPartを使った例と同程度の速度である.PadRightの使い方については「行列の拡張」で述べた.
行列の内容
Wolfram言語の行列は,Wolfram言語がサポートしているさまざまなすべてのオブジェクトの型を使って構築できる.行列は機械精度の浮動小数点数,任意精度の浮動小数点数,複素浮動小数点数,整数,有理数,一般の記号的数量を含むことができる.これらの型については「行列の型」を参照のこと.
Wolfram言語がこれらの多岐に渡る型の行列を扱えるということは,システムの強みである.しかし,数値的な浮動小数点計算の効率に影響を与える.まず,記号および数値的な要素の混在,次に異なる型の数の混在,そして整数行列がその要因である.
記号・数値が混在した行列
記号的,数値的要素を1つの行列の中に混在させると,「モード混在行列」で説明しているように,線形代数関数はその行列を記号的であるとみなす.記号行列を扱うための技術は通常数値行列用に高度に最適化されたものと比べて遅い.
これを解決するには,行列が数値のみを含んでいるようにすればよい.
数値の型が混在した行列
行列の中に異なる型の数を混在させると,線形代数関数はその行列を数値行列として扱い,精度を最も低いものに合わせる.これについては「モード混在行列」に記述してある.これは線形代数関数に対して,記号行列のときほどのパフォーマンスへの影響はない(これについては前セクションで述べた).
次の例では,200×200の2つの行列の掛け算を比較する.2つ目の行列は整数を含んでいるが,行列同士の掛け算を行うと遅い.操作がよりコストの高いものの場合,この差は顕著ではないこともある.
この問題を解決するには,実数で処理したいときは実数で初期化すればよい.
整数行列
整数行列を含む計算において厳密な結果を返すことはWolfram言語の利点である.しかし,純粋な数値系の多くに対しては近似の結果が返される.
近似実数の行列を扱う際は,計算を近似実数で始めなければならない.
式の効率
「Wolfram言語式としての行列」ではWolfram言語が行列を式として表すことについて述べてある.この式は,システム全般に渡って一貫して使われるデータ型である.このシステム内での一貫した形式の利点についてはすでに述べた.Wolfram言語式の使用は,効率にさまざまに影響する.
行列の更新
前述のベクトル化操作を使うと,行列に対する繰返しの操作が回避でき,コピー回数が最小に抑えられる.行列に対する繰返し操作が回避不可能な場合は,そのループをできるだけ簡単にし,行列の無駄なコピーを避けるとよい.
全体の処理に必要な時間は,各ステップでコピーしなければならないリストの長さの2乗に比例して増加する.コピーしなくてもよい場合は線形的に増加する.入力リストが長くなると,その影響も大きくなり,コードのパフォーマンスが劣化し始める.
行列への追加
Wolfram言語では,式は配列として実装されている.これにより,式のある特定の要素に素早くアクセスできる.行列はWolfram言語式なので,配列でもある.配列は要素に素早くアクセスできるが,要素を追加するのには時間がかかる.一般にこの操作にはコピーが必要とされるが,それは避けるべきことである.次の例では,要素を加えるためのコストを示す.
全体の処理に必要な時間は,各ステップでコピーしなければならないリストの長さの2乗に比例して増加する.コピーしなくてもよい場合は線形的に増加する.入力リストが長くなると,その影響も大きくなり,コードのパフォーマンスが劣化し始める.