Python/Julia memo

Julia

ShellModel.jl https://github.com/SotaYoshida/ShellModel.jlの開発で得たTips

基本的に速度が出ないときは

  1. 公式のPeformance Tipsを参照する。

  2. (当たり前っちゃ当たり前だが)不必要なメモリの割当を避ける

たとえばループの中で行列のコレスキー分解の(陽な)計算が必要な場合

#A: m×m given matrix

for i =1:10^6

tL = cholesky(A).L

end

などとすると、10^6回メモリの割当が発生してしまう。

#A: m×m given matrix, L: LowerTriangular matrix

#my_cholesky!: destructive version of Cholesky decomposition

tL = LowerTriangular(zeros(Float64,m,m))

for i =1:10^6

my_cholesky!(A,tL)

end

などとして、pre-allocatedな行列を更新する"destructive"な関数を作り使用すると大幅に速度が改善する。

  1. 同様にして、ブロードキャストを用いることで不要なメモリの割当を避ける

  2. BLASの関数への置き換えを検討する

  3. 3つのInt64を深い階層のArrayにして持って使うよりも、3つのInt64自体を一つのstructに押し込んで使うほうが速い事が多かった。
    (理由はあんまりよく分かってない.boundary checkとか?)

  4. 関数を定義するときに

function f(ln::::Array{Array{Int64,1}},

occs::FA) where {FA<:Array{Float64,1}}

...

end

と型のアノテーションを付与できるが「パフォーマンスの改善に必要」というのは、やや不正確かも。
(型の不確実性を避けることで速くなる、という意味ではあってる)

コードの開発段階で、意図したものと違うAny型の配列などが紛れ込んでパフォーマンスが低下するのを防ぐのに、アノテーションを書くと良い。

抽象型に依る速度低下のサンプルコード

function f1(V1)

for i=1:10^5

for j=1:10^3

for tmp in V1[i][j]

tmp*0.001

end

end

end

end

function f2(V2)

for i = 1:10^5

for j=1:10^3

for tmp in V2[i][j]

tmp*0.001

end

end

end

end

function main()

V1 = [ [ [] for j=1:10^3 ] for i=1:10^5]

for i=1:10^5

for j =1:10^3

push!(V1[i][j],i*j*1.e-2)

end

end

V2 = [ [ Float64[] for j=10^3 ] for i=1:10^5]

for i=1:10^5

for j =1:10^3

push!(V2[i][j],i*j*1.e-2)

end

end

print("V1 \t")

@time f1(V1)

print("V2 \t")

@time f2(V2)

print("V1 \t")

@time f1(V1)

print("V2 \t")

@time f2(V2)

end


main()


入れ子のリストに対して何らかの操作を行う事を考えてみる。

上のコードを実行すると、f2に比べてf1は2桁くらい遅い(factorは環境に依る)。

f1では、演算tmp*0.01部分でメモリアロケーションが発生するのが主な原因。

また、tmp*0.01をコメントアウトして、ループさせるだけだとメモリアロケーションは起こらないが、やはりf1は1桁遅かった。



Julia v1.5.3とv1.6.0rc-1で、パフォーマンスが異なるコードを発見した。

vs = [ zeros(Float64,dim) for i=1:num]のような1次元配列のArray (Array{Array{Float64,1}})にループ内でアクセス

v1 = vs[i]; v2=vs[i+1]してdot積を取るような場合、v1.5.3ではv1,v2の型はArray{Float64,1}なのに対し、

v1.6.0 rc-1ではVector{Float64}で、後者のほうがdot積が桁で速くなっていた。

Julia PERLでa = [ zeros(Float64,10^6) for i=1:10]とやっても確認できる。

Vector{Float64}は単にArray{Float64}のaliasっぽいけど...??

Python

M1 Macでのmatplotlib環境を構築したときのメモ書き

(自分用のメモなので、実行して不具合があっても責任は取れません)

  1. https://stackoverflow.com/questions/66204654/unable-to-install-matplotlib-with-python3-on-m1-mac-using-pip3

を参考にすると、今まで使っていたお絵かきスクリプトがpython3 hogehoge.pyで動くようになった。

  1. Julia側からPyCallでmatplotlibを使う場合、普段使いのPythonとJuliaで用いるPythonのversionが異なる。

M1 MacでのPython環境構築は現状手探りなところもあるので、分けて使うのが当面良さそう。

実際、普段使いのpython3にENV["PYTHON"]で用いるPythonを指定してPyCallをbuildすると、PyCallを呼ぼうとするとJuliaが強制終了するようになってしまった。

(一旦、Juliaのバイナリとホームディレクトリにある~/.juliaを消す羽目になった)

  1. 再度バイナリをインストールしてライブラリを再インストール → using PyCall でJulia内のpythonが読み込まれるところまで復旧

JuliaはCondaを持っているので、そこで必要なライブラリを整備することにする。まずconda initする

$~/.julia/conda/3/bin/conda init bash

(当然、最後の部分は使っているシェルに依存)

  1. $conda activate

でconda環境に入り、

(base) ~ $ conda install matplotlib

でmatplotlibを導入

  1. JuliaでPyCallをrm→再インストールで、

julia>using PyCall

julia>@pyimport matplotlib

が出来るようになった。

これをやったのは2021年の3月くらいで、その後変わってそう。