カンマ区切り、shellscript vs ruby
Qiitaのこの記事を読んだ
シェルスクリプトを何万倍も遅くしないためには —— ループせずフィルタしよう - Qiita
「カンマ区切りデータの2列目に含まれる小文字の母音("aeiou")を数える」
というお題で速さを比較してたので、せっかくだからrubyで書いて比べてみた
テストデータ
File.write 'data.dat', ([%(abc,atmark,123), %(def,colon,456)] * 1000_000).join("\n")
ruby vs shellscript
#!/bin/sh cut -d, -f2 |tr -dc 'aeiou' |wc -c
puts STDIN.each_line.map { |line| line.split(',', 2).last.count('aeiou') }.sum
結果
./v5.sh < data.dat 1.86s user 0.03s system 175% cpu 1.079 total ruby slow.rb < data.dat 2.10s user 0.05s system 98% cpu 2.188 total
rubyの方が倍くらい遅かった
ruby(リベンジ)
count = 0 target = 'aeiou' s = '' STDIN.each_line do |line| s << line.split(',', 2).last if s.size > 0x10000 count += s.count target s = '' end end puts count + s.count(target)
String#countは短い文字列に対して何度も呼ぶよりconcatしてまとめて呼ぶ方が良さそう
1000000.times{''.count 'very_long_string'} #=> 0.73秒 1000000.times{''.count 'a'} #=> 0.16秒 ('a'*1000000).count 'a' #=> 0.001秒
結果は...
./v5.sh < data.dat 1.86s user 0.03s system 175% cpu 1.079 total ruby a.rb < data.dat 1.28s user 0.05s system 97% cpu 1.363 total
あとちょっと及ばない
それとsplitしないバージョンも書いてみたけど速度出なかった
ここでshellscriptの171% cpuに注目 rubyでも並列させてみることに
rubyリベンジ2
path = ARGV[0] filesize = File.size(path) cores = 4 pipes = Array.new cores do |id| per = (filesize + cores - 1) / cores from = per * id to = [per * (id + 1), filesize].min rio, wio = IO.pipe fork do file = File.open(path, 'r') file.seek from length = to - from length -= file.gets.size if id != 0 count = 0 target = 'aeiou' s = '' file.each_line do |line| length -= line.size s << line.split(',', 2).last if s.size > 0x10000 count += s.count target s = '' end break if length < 0 end wio.puts count + s.count(target) end rio end puts pipes.map { |io| io.gets.to_i }.sum
(STDINやめてFileにするインチキだけど)
fork→file.seekして4分割それぞれ数える→集約、分割の境界をうまいことどうにかする
./v5.sh < data.dat 1.86s user 0.03s system 175% cpu 1.079 total ruby b.rb < data.dat 2.51s user 0.07s system 329% cpu 0.782 total
とようやくshellscript超えできた
他の結果
go速い、C速い(-O3つけなかったらgoよりだいぶ遅くなるけど)
goroutineで(cpu327%も使って、ほんの少しだけだけど)Cより速く動いた
SSDかHDDかでどれくらい変わるんだろ
% cc -O3 a.c && time ./a.out < data.dat % go build a.go && time ./a < data.dat % go build b.go && time ./b < data.dat % time ./v5.sh < data.dat % time ruby a.rb < data.dat % time ruby b.rb data.dat # これだけSTDINじゃなくfileをコマンドライン引数で渡してる GO B: 0.13s user 0.01s system 327% cpu 0.043 total 25.1倍 C: 0.04s user 0.01s system 93% cpu 0.053 total 20.4倍 GO A: 0.05s user 0.01s system 93% cpu 0.062 total 17.4倍 RUBY B: 2.51s user 0.07s system 329% cpu 0.782 total 1.38倍 SHELL: 1.86s user 0.03s system 175% cpu 1.079 total 基準 RUBY A: 1.28s user 0.05s system 97% cpu 1.363 total 0.79倍
コードはこちら
https://gist.github.com/tompng/2b26c0bf1c1f81b0f8827271d1cb900a
(b.goが特に長ったらしいけどうまいやり方ないかなぁ)