「関数型Ruby」という病(7) - Elixir's Pipe operator |> in Ruby
最近Elixirが人気ですよね。Erlang VM上でOTPの恩恵を受けながら簡潔な記法で並行処理を書ける言語ということで話題になっていますな? Elixirは関数型プログラミングのエッセンスを取り入れていると言われており、そのひとつにPipe演算子(|>) がある。
Pipe演算子(|>)とは何かというと、左辺の値を右辺の関数の第1引数に適用する演算子。
iex> [1, [2], 3] |> List.flatten() [1, 2, 3]
上記のコードは、左辺の[1, [2], 3]
を 右辺の List.fatten(list)
の引数として渡す。
このPipe演算子は、Stream
モジュールなどと合わせて利用するとデータが左から右へ流れている模様をコードとし視覚化することができるという利点があるっぽい(感じろ)。
iex(16)> f = fn a -> IO.puts "f(#{a}) : #{a+1}"; a ; end #Function<6.90072148/1 in :erl_eval.expr/5> iex(17)> g = fn a -> IO.puts "g(#{a}) : #{a*2}"; a * 2 ; end #Function<6.90072148/1 in :erl_eval.expr/5> iex(18)> 1..10 |> Stream.map(f) |> Stream.map(g) |> Enum.take(3) f(1) : 2 g(1) : 2 f(2) : 3 g(2) : 4 f(3) : 4 g(3) : 6 [2, 4, 6]
1..10 |> Stream.map(f) |> Stream.map(g) |> Enum.take(3)
というコードで、1から10のStreamに対してlazyに関数fとgを順番に適用しながら3つの要素を取り出すという様を素直に表現できていますね?(思え)
さて、そんな便利なパイプ演算子ですが、実は2年ほど前に作ったlambda_driver.gemに既に実装されていたりする。
- Rubyで関数合成とかしたいので lambda_driver.gem というのを作った - ( ꒪⌓꒪) ゆるよろ日記
- lambda_driver/revapply.rb at master · yuroyoro/lambda_driver · GitHub
Rubyでは中置演算子を独自に定義することはできないので、 Object
クラスに |>
というメソッドを生やすことで実現しよう(全角ェ)。
素朴な実装はこうだ
class Object def |>(f = nil) puts f if block_given? yield self else f.call(self) end end alias_method "|>", :>= end
さて、この全角の |>
を使って、上記のElixirのコードをRubyで書いてみるとどうなるか?
irb(main):059:0> f = ->(a){ puts "f(#{a}) : #{a + 1}" ; a + 1} => #<Proc:0x007ffb8d8b2348@(irb):59 (lambda)> irb(main):060:0> g = ->(a){ puts "g(#{a}) : #{a *2}" ; a * 2} => #<Proc:0x007ffb8d860a98@(irb):60 (lambda)> irb(main):061:0> (1..10).|>(&:lazy).|>{|x| x.map(&f) }.|>{|x| x.map(&g) }.|>{|x| x.take(3)}.|>(&:force) f(1) : 2 g(2) : 4 f(2) : 3 g(3) : 6 f(3) : 4 g(4) : 8 => [4, 6, 8]
なんというか、すごく……ダサいですね……。
というか、Rubyだったら素直にこう書いた方がいい
irb(main):143:0> (1..10).lazy.map(&f).map(&g).take(3).force f(1) : 2 g(2) : 4 f(2) : 3 g(3) : 6 f(3) : 4 g(4) : 8 => [4, 6, 8]
ごくごくまれに、左辺値がObjectとかでrevapplyを使いたくなることもなきにしもあらずだが、そういう場合でも大抵は Object#try
で事足りる。
結論 : Rubyには必要ないのでは?
Elixir、 |>の右辺が2引数以上の関数のときに、左辺が第一引数に埋め込まれるのが違和感。 1..10 |> Enum.map(&(&1 * 3)) だと、Enumの第一引数はlistなのにぱっと見で関数渡しているように見えてしまうのが気になる
— null (@yuroyoro) October 28, 2015
「関数合成は|>で代用できるよ!」って言ってるけどそうじゃないんだ。合成した後の関数をその場で適用するのではなく値として持ち回りたいんだよ…。あと|>につなげる関数をカリー化させたいんだよ
— null (@yuroyoro) August 26, 2015
Elixirのパイプライン(|>)ってようはOcamlのrevapplyってことか。これがあるのに関数合成やカリー化がないのは本当に惜しい
— null (@yuroyoro) August 26, 2015
カジュアルに関数がカリー化、部分適用できれば|>がもっと輝くのに…
— null (@yuroyoro) October 28, 2015
「commit-m: GitHubコミットメッセージの文例が検索できるサービス」がとても便利だったのでcliから使えるコマンド書いた
http://commit-m.minamijoyo.com/:titele という有名OSSのコミットメッセージを検索できるサービスがあって、英語のコミットメッセージを書くときに「あれ? これどういう風に書けばいいんダー」ってときに例文を検索できて捗る。
が、自分の場合はコミットメッセージ書くときはvim
とか git commit -m
とかからなのでCLIで検索できたらより捗るかと思ってGolangで書いた。
APIとかは無いようなのでクロールしてる。 GoQuery
使えばこの手のクローラーが一瞬でかけるのでよさがある。
go get github.com/yuroyoro/gommit-m
で入れた後に gommit-m keyword [page]
で検索できる。
表参道.rb #4で「本当は怖い オープンクラスと Duck Typing」というLTをやった話
スライドです
本当は怖いオープンクラスとDuckTyping - 表参道.rb #4
まぁたいした話じゃないんですが、マッドマックスの画像をスクリーンに大写しできたのでその点だけで個人的には満足しています
「型を讃えよ」
Rspecでfailするとデスメタルが流れるようにした
あまりにもテスト通らないのでデスメタル聴き始めた
— ⁰⁰⁰⁰null (@yuroyoro) 2015, 7月 2
このような事があったので自動化した。
Mac限定。
こんな感じ。successだとレベルがアガる。
事前にbash-itunes というコマンドラインツールを入れておく。
iTunesを日本語で使ってる場合、patchを当てる必要がある。
こちらを山椒
コードはこれな。
class PlayItunesReporter attr_accessor :success_track, :failure_track def initialize(options = {}) @success_track = options[:success] @failure_track = options[:failure] end def dump_summary(notification) return unless notification.examples.length > 0 if notification.failed_examples.length == 0 play!(success_track) else play!(failure_track) end end def play!(track) `itunes play "#{track}"` end end RSpec.configure do |config| itunes = PlayItunesReporter.new(success: "レベル・アップ", failure: 'Nemesis') config.reporter.register_listener itunes, :dump_summary end
正直、曲は好きなの使えばいい。なんならこれでもいい
inspired by : コンパイル中に音楽を流せる sbt プラグインを作りました。 - tototoshi の日記
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の気持ちになってみるとよいかも知れません。