LoginSignup
8
8

More than 5 years have passed since last update.

CSVからLSTVを出力するいくつかの実装

Last updated at Posted at 2014-07-21

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で定期的に実行させると良いのかなと思ってます。

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8