や、言語関係など覚えたいもの、覚えるべきものはたくさんある一方で、注目が集まっているから、やってみたい。ということでプログラミング言語としてのを学んでいく。
4. Juliaの高速化
4.3 メモリ割当ての削減
はメモリの割り当てと回収を自動でおこなうため、メモリ割り当てを意識せずとも利用できる。しかし余計なメモリ割り当てを行っている場合もある。
4.3.1 配列のメモリ割り当て
配列は型や型などの基本的な数値型を収める場合には、各要素のサイズと配列事態の要素数の積に相当するメモリを消費する*1。メモリのサイズは関数で取得できる。
v = zeros(1024) sizeof(v) Base.summarysize(v)
配列のメモリ割り当てを減らす簡単な方法は必要最低限のサイズと要素の型を用いることである。たとえば配列数を冗長に持つのは無駄であり、また計算精度が低くても問題が無いのであれば、型で要素を持つ必要はない。
4.3.2 配列の再利用
ベクトルや行列などの配列はサイズがメモリ割り当ての大半を占めることが多い。そのためメモリ割り当ての削減対象として効果的である。
たとえばが行列であるとき、
for _ in 1:t W = W * P end
をループさせると、行列が毎回新しく割り当てることになる。そこでループの外側で一度行列を割り当てて、そこに行列積の結果を書き出せばメモリの割り当て量を大幅に削減できる。そのためにモジュール内の!関数を用いると良い。
W_tmp = similar(W) # 結果保持用の行列 for _ in 1:t mul!(W_tmp, W, P) copy!(W, W_tmp) end
他にも行列分解の関数(など)は関数名の最後に!を付けると、引数として与えられた行列を計算領域として再利用しながら行列分解を行なうことができる。こうしてメモリ割り当ての削減ができる。
4.3.3 ブロードキャスティングによるメモリ削減
ブロードキャスティングは、複数の演算を1つのループにまとめる機能もある。これを用いることで中間結果を保持する配列の割り当てをなくすこともできる。
4.3.4 特殊な配列型の利用
の標準ライブラリには型以外にも特殊な配列型があり、これらの配列型にそれらを用いることができれば、メモリの使用量を大幅に削減し得る。
たとえば対角行列を表現するには、モジュールにはというデータ型があり、これは対角成分のみを保持するため、保持要素数を削減できる。
using LinearAlgebra Diagonal(ones(5))
4.4 コンパイラへのヒント
4.4.1 境界チェックの省略
では添字を用いて配列の要素を参照する際、その添字が配列の適切な範囲内に収まっていることを毎回チェックする。
この機能は配列の範囲外のメモリ領域を誤って参照しないようにするための安全装置である。しかしこの参照には無視し得ないコストがかかる。もし添字が必ず適切な範囲に収まっていることをプログラマが確認できる場合には、コンパイラにヒントを与えて無駄な境界チェックを書くこともできる。
@マクロはそのようなヒントを与えるマクロである。これを付けられた式は、配列要素の参照で境界チェックを省略する。
function isort!(xs) for i in 2:length(xs) j = 1 @inbounds while j > 1 && xs[j-1] > xs[j] xs[j-1], xs[j] = xs[j], xs[j-1] j -=1 end end return xs end
4.4.2 浮動小数点数演算の高速化
は浮動小数点数の計算をに従って実装しているが、@マクロを用いるとこの規格に従わないコードの最適化をコンパイラに許可する。ただし精度が重要な計算で同マクロを用いると問題を起こす可能性もあるため要注意である。
4.4.3 関数のインライン化
の関数呼び出しは、実行前に型推論でどのメソッドを呼び出すか決定できれば高速である。しかし関数内部の計算が非常に軽い場合、関数呼び出しに掛かるコストが無視できないほどの影響を与える場合がある。そこで関数内部のコードを呼び出し側に展開するインライン化を行なうとパフォーマンスが改善される場合がある。
のコンパイラは、インライン化するか否かを自動で判定する。このときに積極的にインライン化すべきとヒントを与えるとパフォーマンスを改善し得る。
@inline f(x) = x + 1 g(x) = f(x) + 3 @code_llvm g(1)
関数のインライン化をすると、関数内部のコピーが呼び出しごとにつくられるため、生成されるプログラムのサイズは大きくなる傾向にある。
*1:実際にはここにメタ情報が加わるため若干増える。