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

一流の大人(ビジネスマン、政治家、リーダー…)として知っておきたい、教養・社会動向を意外なところから取り上げ学ぶことで“気付く力”を伸ばすブログです。データ分析・語学に力点を置いています。 →諸事情により、2023年4月~8月くらいまで更新を不定期にします。

MENU

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

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

4. Juliaの高速化

4.2 最適化しやすいコード

 \mathrm{Julia}の処理系では、コンパイラは動的にコンパイルするため、何気ないコードが想定外のパフォーマンス低下をもたらす可能性がある。

4.2.4 グローバル変数への参照

 グローバル変数を不用意に用いると、型の不確実性が生じることがある。グローバル変数がどこからでも参照可能で、定数でない限り実行中にいつでもオブジェクトを変更できるからである。
 可能であれば関数内に収めるなどで型を決定させるのが良いが、状況によっては性能が必要なコードでグローバル変数を参照したい場合がある。その場合、ループの内側などで頻繁に参照されるグローバル変数には、可能であれば\mathrm{const}キーワードをつけて定数化することが推奨される。

###########################################
### Constキーワードを用いた場合の型推論 ###
###########################################

using Profile

# (1) Constなしの場合
G1 = 6.674 * 10^-11 # 万有引力定数

# (2) Constありの場合
const G2 = 6.674 * 10^-11 # 万有引力定数


# 2質点間の万有引力
force1(m1, m2 , r) = G1 * m1 * m2 / r^2
force2(m1, m2 , r) = G2 * m1 * m2 / r^2

println(@code_warntype force1(1.0, 2.0, 3.2))
println(" ")
println("***********")
println(" ")
println(@code_warntype force2(1.0, 2.0, 3.2))



 またグローバル変数の型は変化させず、値を変更させたい場合もある。このとき\mathrm{Ref}()を用いることも考えられる。

4.2.5 コレクションや構造体での型不確実性

 \mathrm{Julia}ではコレクションや構造体も不用意に定義すると型不確実性の問題が生じる。
 たとえば

xs = []

と定義すると、\mathrm{xs}の型は\mathrm{Vector}\left\{\mathrm{Any}\right\}となる。
 ここから値を取得するなど(\mathrm{push}!や\mathrm{pop}!)しても方は推定できないままである。そのため、方は決まっているが要素が無い配列を定義するのであれば、\mathrm{Int}[]などと定義すればよい。

4.2.6 メモリレイアウト

 計算機では、多次元配列も1次元のアドレス空間を持つメモリに保持される。そのため、多次元配列は何らかの方法で1次元に変換されて保存・参照されることになる。この変換方法に関与するのがメモリレイアウトである。

 メモリレイアウトを意識することは、配列要素へのアクセスを頻繁に行うプログラムを書く上で非常に重要である。今日の計算機はメモリの読み書きが\mathrm{CPU}上で行う他の計算よりも非常に遅いため、より読み書きが高速になるようにメモリの一部を\mathrm{CPU}内にキャッシュしている。メモリの読み書きを行なう際にはアドレス上の近い領域を集中的に参照した方がキャッシュが効率的に利用される。したがって多次元配列で各要素がどのようにアドレス空間上に配置されているのかを把握した方が良い。

 たとえば多次元配列を走破する場合、可能な限りメモリ上の近い要素が並ぶ方向にすべきである。簡単のため2次元配列を考えると、\mathrm{Julia}において\mathrm{Array}型は行列の要素を\mathrm{column}-\mathrm{major} \mathrm{order}(列の各要素がアドレス空間で隣り合う)で配置している。そのため\mathrm{for}文で走破するには列方向の反復を内側に入れた方がキャッシュを有効に使えて実行速度が上がる*1

########################
### メモリレイアウト ###
########################

function column_first(X)
    m, n = size(X)
    for j in 1:m # 列の添字
        for i in 1:n # 行の添字
        end
    end
end

function row_first(X)
    m, n = size(X)
    for i in 1:n # 行の添字
        for j in 1:m # 列の添字
        end
    end
end

num = 10000
results1 = 0.0
results2 = 0.0

for i in 1:num
    X = Array{Int}(undef, 10000,10000)
    
    result1, runtime1 = @timed column_first(X)
    result2, runtime2 = @timed row_first(X)
    
    results1 += runtime1
    results2 += runtime2
end

println(results1/num)
println(results2/num)


*1:これはPythonのNumPyとは異なる方向であるので注意する。

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