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から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化する、という最適化はありなのかも知れない。
参考
Intel AVX を使用して SIMD 演算を試してみる - kawa0810 のブログ
メモリアライメントを揃えずに SIMD する方法 - kawa0810 のブログ
SSEとAVXで高次元ベクトルの内積計算を高速化してみた | さかな前線
introdunction to SIMD programming - primitive: blog
組み込み関数(intrinsic)によるSIMD入門
gccでintrinsicsでSSEでベクタライズする時の簡易的なまとめ - ぬうぱんの備忘録
コンパイラー最適化入門: 第1回 SIMD 命令とプロセッサーの関係 | iSUS
Google グループ
Intel Intrinsics Guide
x86/x64 SIMD命令一覧表 (SSE~AVX2)
CPUの気持ちになれるツール作った
魂のステージが低いのか、CPUの歓声が聞こえません。
CPUの歓声は聞いたことないですが、ハードディスクの断末魔なら何度も聞いてます
— ⁰⁰⁰⁰null (@yuroyoro) October 19, 2014
なので、CPUの気持ちになれるツール作った。
Remix: Latency Numbers Every Programmer Should Know(2014)
右側にあるボタンっぽいのをぽちぽちして、CPUの気持ちになってみるとよいかも知れません。
self.send(pred)がtrueならばselfを、そうでないならnilを返すメソッド
何をいっているのかというと、こういうことです
rubyで、obj.send(pred) がtrueならばselfを、装で無い場合はnil を返すメソッド欲しい
— ⁰⁰⁰⁰null (@yuroyoro) August 28, 2014
foo.present? ? foo : other みたいなの書くのダルいので
— ⁰⁰⁰⁰null (@yuroyoro) August 28, 2014
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を渡したい場合があるのでこれも無駄にはならない、はず。
@yuroyoro
http://t.co/rhQicrvuWW
— irxground (@irxground) August 28, 2014
8/29さらに追記
というか、ずいぶん前に自分で既に書いてgemにしてあったし俺は一体何をやっているんだ……。
おそらく上位存在からの記憶操作が行われた可能性がある……!!
git-ignoreというコマンドを書いた話
git-ignore add みたいのが欲しい
— azu (@azu_re) August 24, 2014
ちょろっと書けそうだったので書いた。
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
存在していないパターンを追加することが可能。
以下の例では、Haskell.gitignoreをGithubからダウンロードする。
$ 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
"err"という文字列をHighlightしておくとGolangのコードリーディングが捗る
vimの人はこんな感じで
autocmd FileType go :highlight goErr cterm=bold ctermfg=214 autocmd FileType go :match goErr /\<err\>/