を使うのに、今一度
で使い方を整理する。
注意
参照文献はかなり古い(2019年)ため、現在のバージョンでは動作しない関数などが多いとの評判がある。そこでそういった齟齬があった場合は随時コメントする。なお筆者の環境は、
アプリケーション | バージョン |
---|---|
である。
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)
これらの結果は、
- 変数
をゼロに初期化する際に
型の
にしているか
が
型か、
の
型のどちらなのか
といったことによる(他の理由は後述。)。
(1) | ・初期値の返り値の型を |
|
(2) | ・初期値の返り値の型を |
|
(3) | ・初期値の返り値の型を |
|
(4) | ・初期値の返り値の型を |
|
(5) | ・初期値を |
このように型が不安定であると、コードの性能が大きく低下する*1。
また今回の計算例における別要因は、
関数を用いると、
が用いるアセンブリコードで分岐命令が使われなくなり、条件式が真の場合と偽の場合の双方の値を計算することになるため、両方の計算が軽い場合、分岐命令を用いない方がループ実行のスピードを遥かに速くする。
- @
を用いると、
命令*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)