Railsで、ファイルの形式変換をする処理をRubyで実装していたのですが、何となく遅い。
裏でいつの間にか変換しておいてくれれば、Rubyにこだわる必要ないんだよなあ。
という事で、いくつかの実装系でどれくらい性能が違うのか試して見ました。
ターゲットは「OS寄りっぽい処理系のAWK」「スクリプト言語(というかRuby)」「コンパイル言語からGo」です。チョイスは好みで決めました。
検証処理
- 100,000行のCSVがある。
$ head data.csv
pref,code,id,value,cost,rank
新潟,00662,6425,7086,9441,94
京都,07758,577,4514,8715,55
:
- これのをLTSV形式に変換したファイルを出力したい。
$ head output.ltsv
pref:新潟<TAB>code:code<TAB>id:6425<TAB>value:7086<TAB>cost:9441<TAB>rank:94
pref:京都<TAB>code:07758<TAB>id:577<TAB>value:4514<TAB>cost:8715<TAB>rank:55
:
実装
Ruby
csv2lsv.rb
require 'csv'
work_dir = File.dirname(__FILE__)
File.open( File.join(work_dir,"ruby_output.ltsv"), "w" ) do |f|
CSV.foreach( File.join(work_dir,"test_data.csv"), headers: true) do |line|
f.puts line.map{|k,v| "#{k}:#{v}"}.join("\t")
end
end
実行
$ ruby csv2lsv.rb
AWK
csv2lsv.awk
BEGIN {
FS = ","
}
NR==1{
for(i=1;i<=NF;i++){
header[i] = $i
}
}
NR>1{
printf ("%s:%s", header[1], $1)
for(i=2;i<=NF;i++){
printf("\t%s:%s", header[i], $i)
}
printf("\n")
}
実行
$ awk -f csv2lsv.awk test_data.csv > awk_output.ltsv
Go
[追記:2014/07/26] 下記Goプログラムは、巨大CSVの処理時にRubyやAwkプログラムと挙動が異なり(出力行数や出力行順がちがう)、また出力結果に冪等性がないことがわかりました。修正中です…csv2lsv.go
package main
import (
"bytes"
"encoding/csv"
"flag"
"io"
"os"
)
func main() {
flag.Parse()
read_file, _ := os.Open(flag.Arg(0))
write_file, _ := os.OpenFile(flag.Arg(1), os.O_WRONLY|os.O_CREATE, 0600)
defer read_file.Close()
defer write_file.Close()
reader := csv.NewReader(read_file)
writer := write_file
// CSV to LSTV
line := 0
var headers []string
for {
// read a line
columns, err := reader.Read()
// EOF : exit loop
if err == io.EOF {
return
}
switch line {
case 0:
headers = columns
default:
ltsv := bytes.NewBufferString("")
line_head := true
for i := 0; i < len(headers); i++ {
if line_head {
line_head = false
} else {
ltsv.WriteString("\t")
}
buffer := headers[i] + ":" + columns[i]
ltsv.WriteString(buffer)
}
writer.WriteString(ltsv.String() + "\n")
}
line++
}
}
実行
$ go run csv2ltsv.go test_data.csv go_output.ltsv
または
$ go build csv2ltsv.go ; ./csv2lstv test_data.csv build_go_output.ltsv
実行速度
$ time コマンド
を5回実行し、そのうち最良/最悪の2回を省いた3測定の平均時間を計算しました。
- ruby 2.1.2p95
- GNU Awk 3.1.8
- go1
実装 | 実行時間(s) |
---|---|
ruby | 4.42 |
awk | 0.88 |
go run | 1.80 |
go build | 1.48 |
※ go buildはビルド時間を含めない
思ったこと
Goは不慣れのため、ライブラリを使うなど最適化できる余地があるかもしれません。
アルゴリズムも微妙にそれぞれ違いますし。
実装の単純さは Ruby >= AWK >> Go
性能は AWK > Go > Ruby
単純なファイル操作はAWKが優秀ですね! なんでこんなに速いのですが、きっと内部で変態的なことが起きているのでしょう。
という訳で、AWKでごにょごにょ作ったファイル処理をRubyのChildprocessをつかって裏で実行させるか、whenenverで定期的に実行させると良いのかなと思ってます。