( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

GolangでSIMDプログラミング

以前から気になっていたSIMDプログラミングをGoでやってみた。

Single Instruction Multiple Data (SIMD) 演算とは1回の命令で複数のデータを同時に処理する演算です.近年の CPU には SIMD 演算を行うことができる SIMD 演算器が搭載されており,Intel 社の CPU ならば Streaming SIMD Extensions (SSE) を用いることで SIMD 演算を行うことが可能です.SSE は CPU に搭載されている 128bit レジスタを用いて演算を行うため単精度データならば4つ,倍精度データならば2つずつ演算を行うことができます.また,近年 SSE 後継の SIMD 拡張命令として Intel Advanced Vector eXtentions (AVX) が登場しました.AVX は第2世代 Intel Core i シリーズのプロセッサ (Sandy Bridge) から使用することが可能であり,演算幅が SSE の2倍の 256bit となっています.つまり,単精度データならば8つ,倍精度データならば4つずつ演算を行うことが可能です.

http://kawa0810.hateblo.jp/entry/20120303/1330797281

といっても、GoのコンパイラSIMDを使ったバイナリを吐くかというとそうではないので、アセンブラなりCなりでSIMDを使うように書いてcgoから使う、という形になる。今後も、GoがSIMD対応することは無さそう(Google グループ)

アセンブラを書くのはつらい(というか書けない)ので、gcc組み込みのSIMD intrinsicsをcgoから使う。intrinsics関数を利用するとcからSSE/AVXを利用できる。今回はSandy Bridgeから利用できるAVXを使って、32bitのfloatの加算をやってみた。

コード

cgoのCFLAGSに -mavxを指定して、gccにAVXを使用するように指定し、immintrin.hをincludeするだけで、cgoからAVXを使える。簡単。


注意点

cgoからintrinsicsを使う際には、注意しなければならないことがある。intrinsicsでSIMD演算を行う場合、

という流れになる。
AVXだと32bit * 8 = 256bitを一度にレジスタにロードして、結果を取り出すことになる。
ここで、メモリとレジスタのやりとりにおいては、対象アドレスが32byteにAlignされている必要がある。
32byte Alignとは、ポインタのアドレスが32で割り切れるようになっている、という意味。

intrinsicsでは、floatの配列を__m256という型にキャストすれば、レジスタとのやりとりをよしなにやってくれるようだが、このときのfloat配列のアドレスは、上述のように32byte alignedである必要がある。

さて、Goからintrinsics関数を利用するときに、このfloat配列のアドレスをどうするのか、という問題が発生する。
対策は2つ。

  • C側でfloat配列を確保する
  • GoでSliceを確保し、Sliceの先頭アドレスを渡す

C側で確保する場合は、話は簡単で、_mm_malloc関数を使うと指定bitでalignして確保できる。他にもgcc4.7からalignを指定する機能もある。ただし、Cで確保した配列をGoのSliceとして扱うには、ちょっとした変換が必要である。また、C側で確保したメモリなのでGoのGC管理下にない。解放のタイミングはプログラマが責任を持つ必要がある。ちょっと、つらい。

GoのSliceを利用する場合、確保されたアドレスが32byte alignedである保証はない。したがって、intrinsicsで非alignedでも利用できる命令(_mm256_loadu_ps)を用いる。この場合は、それなりのオーバーヘッドが発生する。

ベンチマーク結果

Cでメモリ確保する版(BenchmarkAvxAdd)、GoのSliceを渡す版(BenchmarkNonAlignedAvxAdd)、Goのforループで加算する版(BenchmarkGoAdd)でベンチマークを取ってみた。

BenchmarkAvxAdd 3000000 415 ns/op
BenchmarkNonAlignedAvxAdd 1000000 1143 ns/op
BenchmarkGoAdd 1000000 2059 ns/op

32byteにalignした場合が最も速くて、Goのループに比べて約5倍高速。32byte alignedで無い場合は、alignedに比べて2倍強遅くなっている。

ベンチマーク結果をみると、かなりの高速化が期待できる。
Goでwaifu2xのような画像処理を高速に書く場合は、一部の演算をSIMD化する、という最適化はありなのかも知れない。

CPUの気持ちになれるツール作った

魂のステージが低いのか、CPUの歓声が聞こえません。


なので、CPUの気持ちになれるツール作った。

Remix: Latency Numbers Every Programmer Should Know(2014)

f:id:yuroyoro:20141020082407p:plain

右側にあるボタンっぽいのをぽちぽちして、CPUの気持ちになってみるとよいかも知れません。

Typolevel Scala Compiler

最近Scala Complierをforkするのが流行っているようですね。


「俺のコンパイラか?欲しけりゃくれてやる、探せ!この世の全てをそこにおいてきた!」
男達はscalacをforkし、夢を追い続ける。世は正に大コンパイラ時代!


乗るしかないこのビッグウェーブに!! 俺もScalaをforkしてTypolevel Scala Compilerを作ったぜ!!
コンパイラ王に、俺はなるッ!!!

yuroyoro/typolevel · GitHub

f:id:yuroyoro:20140909193526p:plain

久しぶりのScalaネタがこれとか救いようの無い老害だなテメーは

self.send(pred)がtrueならばselfを、そうでないならnilを返すメソッド

何をいっているのかというと、こういうことです


class Object
  def filter(&pred)
    (yield self) ? self : nil
  end
end

書いてみた。

foo".filter(&:present?) # => "foo"
"".filter(&:present?)   # =>  nil


ようは、 str.present? ? str : other みたいなやつを書きやすくするためのものです

str = "hoge" 
str.filter(&:present?) || "fuga" # => "hoge"

str = "" 
str.filter(&:present?) || "fuga" # => "fuga"

よい名前が思い浮かばなかった(´・ω・`)

8/29追記

ActiveSupportにObject#presenceというのがあるそうだ。present?だけならこれで充分。自分は、任意のlambdaを渡したい場合があるのでこれも無駄にはならない、はず。


8/29さらに追記

というか、ずいぶん前に自分で既に書いてgemにしてあったし俺は一体何をやっているんだ……。
おそらく上位存在からの記憶操作が行われた可能性がある……!!

yuroyoro/lambda_driver · GitHub

git-ignoreというコマンドを書いた話


ちょろっと書けそうだったので書いた。

yuroyoro/git-ignore · GitHub

Demo

Installation

PATH通った場所においてくれ

curl -sL https://raw.githubusercontent.com/yuroyoro/git-ignore/master/git-ignore > ~/bin/git-ignore

Examples

`git ignore add "pattern"`で、.gitignoreへ追加する。

$ git ignore add '*.log'


.gitignoreから削除するには、`git ignore remove "pattern"`を実行する。

$ git ignore remove '*.log'


add/removeには複数のパターンを同時に渡すことができる。

$ git ignore add '*.log' '*.bak'


`git ignore list`で定義されているパターンを出力。

$ git ignore list


`--global` オプションを使うことで、グローバルな `.gitignore` (`$HOME/.gitignore`)に対してadd/removeすることも可能。
以下のコマンドで、`*.class` を `$HOME/.gitignore` へ追加する。

git ignore --global add "*.class"


`git ignore pull ` コマンドでは、github/gitignoreから引数の言語に応じた.gitignoreファイルを取得して、
存在していないパターンを追加することが可能。
以下の例では、Haskell.gitignoreGithubからダウンロードする。

$ git ignore pull Haskell


引数なしで`git ignore pull`を実行すると、引数に指定可能な一覧を確認することができる。

$ git ignore pull

rspecの--tagオプションを利用して任意のコマンドライン引数をspec側に渡すという邪悪なhack

今まで、rspecコマンドでは任意の引数を渡すことはできなかったので、環境変数経由で引き渡すという方法をとっていた。
( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

MY_OPT1=true rspec spec/my_spec.rb

環境変数で渡すのはダルいのでなんとかしたいと思い、`--tag`オプション経由で値を渡すというダーティなhackを書いた。

仕掛けは、helper.rbなどで、以下のように`Rspec.world.filter_manager.incusions`から引数を切り出してクラス変数に保持しておくようなアレをホゲる。

helper.rb

class MyOptions
  class << self
    OPTION_KEYS = [:my_opt1, :my_opt2]
    attr_accessor :options
    def parse(world)
      @options = world.filter_manager.inclusions.slice(*OPTION_KEYS)
    end
  end
end

RSpec.configure do |config|

  # filterをonに
  config.filter_run :focus => true
  config.run_all_when_everything_filtered = true

  # --tagから独自の引数を切り出して保持しておく
  MyOptions.parse(RSpec.world)
end

あとは、`MyOptions.options[:my_opt1]`のように参照できる。

my_options_spec.rb

require File.join(File.dirname(__FILE__), 'helper')

describe "MyOptions" do
  subject { MyOptions.options }

  it { should include(:my_opt1) }
  it { should include(:my_opt2) }
end


実行すると、`--tag`経由で引数が渡っていることが確認できる。

$ rspec spec/my_options_spec.rb -t my_opt1 -t my_opt2
Run options: include {:focus=>true, :my_opt1=>true, :my_opt2=>true}

All examples were filtered out; ignoring {:focus=>true, :my_opt1=>true, :my_opt2=>true}

MyOptions
  should include :my_opt1
  should include :my_opt2

Top 2 slowest examples (0.0029 seconds, 100.0% of total time):
  MyOptions should include :my_opt1
    0.00205 seconds ./spec/my_options_spec.rb:6
  MyOptions should include :my_opt2
    0.00085 seconds ./spec/my_options_spec.rb:7

Finished in 0.0034 seconds
2 examples, 0 failures


`--tag`渡さないとfailする。

$ rspec spec/my_options_spec.rb
Run options: include {:focus=>true}

All examples were filtered out; ignoring {:focus=>true}

MyOptions
  should include :my_opt1 (FAILED - 1)
  should include :my_opt2 (FAILED - 2)

Failures:

  1) MyOptions should include :my_opt1
     Failure/Error: it { should include(:my_opt1) }
       expected {} to include :my_opt1
       Diff:
       @@ -1,2 +1 @@
       -[:my_opt1]

     # ./spec/my_options_spec.rb:6:in `block (2 levels) in <top (required)>'

  2) MyOptions should include :my_opt2
     Failure/Error: it { should include(:my_opt2) }
       expected {} to include :my_opt2
       Diff:
       @@ -1,2 +1 @@
       -[:my_opt2]

     # ./spec/my_options_spec.rb:7:in `block (2 levels) in <top (required)>'

Top 2 slowest examples (0.00381 seconds, 100.0% of total time):
  MyOptions should include :my_opt1
    0.00282 seconds ./spec/my_options_spec.rb:6
  MyOptions should include :my_opt2
    0.00099 seconds ./spec/my_options_spec.rb:7

Finished in 0.00432 seconds
2 examples, 2 failures

Failed examples:

rspec ./spec/my_options_spec.rb:6 # MyOptions should include :my_opt1
rspec ./spec/my_options_spec.rb:7 # MyOptions should include :my_opt2