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

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

MENU

Juliaを使うときのTipsをまとめる(その12/X)

 \mathrm{Julia}を使うのに、今一度

で使い方を整理する。

注意

 参照文献はかなり古い(2019年)ため、現在のバージョンでは動作しない関数などが多いとの評判がある。そこでそういった齟齬があった場合は随時コメントする。なお筆者の環境は、

アプリケーション バージョン
\mathrm{Julia} 1.8.0
\mathrm{Jupyter} \mathrm{Notebook} 6.5.2

である。

3. Juliaによる数値演算

3.2 条件文のあるループの効率的な実行

 ループする際、型の安定性によってループのスピードが大きく変わる。
 性能比較のため、例として配列中の正の値の和を取る。

using Random, BenchmarkTools
Random.seed!(1)
x = randn(10^6)

# 性能比較のため、配列中の正の値の和を取る
@btime sum(v for v in x if v > 0)

# 明示的にループを用いる
function pos_sum1(x)
    s = zero(eltype(x))
    for v in x
        if v > 0
            s += v
        end
    end
    s
end

@btime pos_sum1(x)


# ifelseを用いると、ループが遅くなる
function pos_sum2(x)
    s = zero(eltype(x))
    for v in x
        s += ifelse(v > 0,v,zero(s))
    end
    s
end

@btime pos_sum2(x)


# @simdマクロアノテーションをループに追加してみると…
function pos_sum2b(x)
    s = zero(eltype(x))
    @simd for v in x
        s += ifelse(v > 0,v,zero(s))
    end
    s
end

@btime pos_sum2b(x)


# 最もシンプルな実装方法
function pos_sum2c(x)
    s = 0
    for v in x
        s += ifelse(v > 0,v,0)
    end
    s
end

@btime pos_sum2c(x)


 これらの結果は、
 

  • 変数sをゼロに初期化する際に\mathrm{Int}型の0にしているか
  • \mathrm{ifelse(v\gt0,v,0)}\mathrm{typeof(v)}型か、0\mathrm{Int}型のどちらなのか

といったことによる(他の理由は後述。)。

(1) ・初期値の返り値の型を\mathrm{x}に一致

・内包的ループ

\mathrm{if}文による正の判定
5.164\mathrm{ms}
(2) ・初期値の返り値の型を\mathrm{x}に一致

・ループの明示化

\mathrm{if}文による正の判定
1.823\mathrm{ms}
(3) ・初期値の返り値の型を\mathrm{x}に一致

・ループの明示化

\mathrm{ifelse}文による正の判定

\mathrm{zero}()関数で正の値を判定する値を指定
1.203\mathrm{ms}
(4) ・初期値の返り値の型を\mathrm{x}に一致

・マクロアノテーションを追加

・ループの明示化

\mathrm{ifelse}文による正の判定

\mathrm{zero}()関数で正の値を判定する値を指定
275.300\mathrm{\mu s}
(5) ・初期値を0と指定(型不明記)

・ループの明示化

\mathrm{ifelse}文による正の判定

・判定する0は型不明記
5.991\mathrm{ms}

 このように型が不安定であると、コードの性能が大きく低下する*1
 また今回の計算例における別要因は、

  • \mathrm{ifelse}関数を用いると、\mathrm{Julia}が用いるアセンブリコードで分岐命令が使われなくなり、条件式が真の場合と偽の場合の双方の値を計算することになるため、両方の計算が軽い場合、分岐命令を用いない方がループ実行のスピードを遥かに速くする。
  • @\mathrm{simd}を用いると、\mathrm{SIMD}命令*2を用いるように指示する。これを用いるとパフォーマンスが向上する場合がある。

Juliaの公式ドキュメントより
forループの前に@simdを書くことで,反復が独立しており,順序を変えても良いことを約束します.多くの場合,Juliaは@simdマクロを使わなくても自動的にコードをベクトル化できることに注意してください.それは,浮動小数点の再関連付けを許可したり依存するメモリアクセスを無視したり(@simd ivdep)するような場合など,そのような変換がイリーガルな場合にのみ有効です.繰り返しになりますが,@simdをアサートする際には非常に注意が必要で,依存関係のあるループに間違ってアノテートしてしまうと予期せぬ結果につながる場合があります.特に,いくつかのAbstractArrayサブタイプのsetindex!は本質的に反復順序に依存していることに注意してください.この機能は実験的なものであり,将来のJuliaのバージョンでは変更されたり消えたりする可能性があります.


また以下のような表記法もある:

function pos_sum2d(x::AbstractArray{T}) where T
    s = zero(T)
    @simd for v in x
        s += ifelse(v >0, v, zero(T))
    end
    s
end

@btime pos_sum2d(x)

*1:コードが型安定化否かは別で述べる。

*2:複数の並んだ同種の計算について、一度に計算を行う機能。

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