「大人の教養・知識・気付き」を伸ばすブログ

一流の大人(ビジネスマン、政治家、リーダー…)として知っておきたい、教養・社会動向を意外なところから取り上げ学ぶことで“気付く力”を伸ばすブログです。データ分析・語学に力点を置いています。 →現在、コンサルタントの雛になるべく、少しずつ勉強中です(※2024年1月21日改訂)。

MENU

Juliaを基礎からゆっくりと(その18/18)

 \mathrm{R}\mathrm{Python}\mathrm{C}言語関係など覚えたいもの、覚えるべきものはたくさんある一方で、注目が集まっているから、やってみたい。ということでプログラミング言語としての\mathrm{Julia}を学んでいく。

4. Juliaの高速化

4.3 メモリ割当ての削減

 \mathrm{Julia}はメモリの割り当てと回収を自動でおこなうため、メモリ割り当てを意識せずとも利用できる。しかし余計なメモリ割り当てを行っている場合もある。

4.3.1 配列のメモリ割り当て

 配列は\mathrm{Int8}型や\mathrm{Float64}型などの基本的な数値型を収める場合には、各要素のサイズと配列事態の要素数の積に相当するメモリを消費する*1。メモリのサイズは\mathrm{Base.summarysize}関数で取得できる。

v = zeros(1024)
sizeof(v)
Base.summarysize(v)

配列のメモリ割り当てを減らす簡単な方法は必要最低限のサイズと要素の型を用いることである。たとえば配列数を冗長に持つのは無駄であり、また計算精度が低くても問題が無いのであれば、\mathrm{Float64}型で要素を持つ必要はない。

4.3.2 配列の再利用

 ベクトルや行列などの配列はサイズがメモリ割り当ての大半を占めることが多い。そのためメモリ割り当ての削減対象として効果的である。
 たとえばW,Pが行列であるとき、

for _ in 1:t
    W = W * P
end

をループさせると、行列Wが毎回新しく割り当てることになる。そこでループの外側で一度行列を割り当てて、そこに行列積の結果を書き出せばメモリの割り当て量を大幅に削減できる。そのために\mathrm{LinearAlgebra}モジュール内の\mathrm{mul}!関数を用いると良い。

W_tmp = similar(W) # 結果保持用の行列
for _ in 1:t
    mul!(W_tmp, W, P)
    copy!(W, W_tmp)
end

他にも行列分解の関数(\mathrm{svd},\mathrm{qr}など)は関数名の最後に!を付けると、引数として与えられた行列を計算領域として再利用しながら行列分解を行なうことができる。こうしてメモリ割り当ての削減ができる。

4.3.3 ブロードキャスティングによるメモリ削減

 ブロードキャスティングは、複数の演算を1つのループにまとめる機能もある。これを用いることで中間結果を保持する配列の割り当てをなくすこともできる。
 

4.3.4 特殊な配列型の利用

 \mathrm{Julia}の標準ライブラリには\mathrm{Array}型以外にも特殊な配列型があり、これらの配列型にそれらを用いることができれば、メモリの使用量を大幅に削減し得る。
 たとえば対角行列を表現するには、\mathrm{LinearAlgebra}モジュールには\mathrm{Diagonal}というデータ型があり、これは対角成分のみを保持するため、保持要素数を削減できる。

using LinearAlgebra

Diagonal(ones(5))

4.4 コンパイラへのヒント

4.4.1 境界チェックの省略

 \mathrm{Julia}では添字を用いて配列の要素を参照する際、その添字が配列の適切な範囲内に収まっていることを毎回チェックする。
 この機能は配列の範囲外のメモリ領域を誤って参照しないようにするための安全装置である。しかしこの参照には無視し得ないコストがかかる。もし添字が必ず適切な範囲に収まっていることをプログラマが確認できる場合には、コンパイラにヒントを与えて無駄な境界チェックを書くこともできる。

 @\mathrm{inbounds}マクロはそのようなヒントを与えるマクロである。これを付けられた式は、配列要素の参照で境界チェックを省略する。

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 浮動小数点数演算の高速化

 \mathrm{Julia}浮動小数点数の計算を\mathrm{IEEE754}に従って実装しているが、@\mathrm{fastmath}マクロを用いるとこの規格に従わないコードの最適化をコンパイラに許可する。ただし精度が重要な計算で同マクロを用いると問題を起こす可能性もあるため要注意である。

4.4.3 関数のインライン化

 \mathrm{Julia}の関数呼び出しは、実行前に型推論でどのメソッドを呼び出すか決定できれば高速である。しかし関数内部の計算が非常に軽い場合、関数呼び出しに掛かるコストが無視できないほどの影響を与える場合がある。そこで関数内部のコードを呼び出し側に展開するインライン化を行なうとパフォーマンスが改善される場合がある。
 \mathrm{Julia}コンパイラは、インライン化するか否かを自動で判定する。このときに積極的にインライン化すべきとヒントを与えるとパフォーマンスを改善し得る。

@inline f(x) = x + 1
g(x) = f(x) + 3

@code_llvm g(1)

 関数のインライン化をすると、関数内部のコピーが呼び出しごとにつくられるため、生成されるプログラムのサイズは大きくなる傾向にある。

4.4.4 @simdマクロによる並列処理

 現代の\mathrm{CPU}には、高速化のためにひとつの命令で複数の値を処理するような命令が実装されていることが多い。このような命令は\mathrm{SIMD}命令と呼ばれる。@\mathrm{simd}マクロは、与えられた\mathrm{for}文の中で\mathrm{SIMD}命令を用いた最適化をすることをコンパイラに許可する。これにより実行速度が大きく向上する場合がある。ただし同マクロは、最も内側の\mathrm{for}文にしか適用されない。

*1:実際にはここにメタ情報が加わるため若干増える。

プライバシーポリシー お問い合わせ