私は主に楽しみのためにRubyでCSVライブラリを書いています(私は知っています、標準のものは素晴らしいです!)。現在、標準の約4倍の速度であり、次の理由で奇妙に感じます。stdlibからcsv.rbを調べましたが、正規表現を使用して行を分割していますが、それほど高速ではないと予想されます。私のライブラリではDFAを使用しているので、O(n)時間で実行されると確信しています-バックトラックはほとんどありません。逸脱したケースに対応するために一度バックトラックした場合のみです(escape char == quote char )そしてそれは時間の約1%だけ起こります。
だから私は明らかに私のコードをプロファイリングしました、そしてこれが総実行時間の89%を占める部分です。これは、入力ファイルのすべての文字に対して実行されるループです。
def consume token
if !@separator and [:BEFORE_FIELD, :FIELD, :BEFORE_SEPARATOR].include?(@state)
if @potential_separators.include? token
@separator = token
end
end
#puts "#{@state} - Token: #{token}"
@state = case @state
when :QUOTED_FIELD
if @escape.include? token
@last_escape_used = token
:MAYBE_ESCAPED_QUOTE
elsif token == @quote
:BEFORE_SEPARATOR
else
@field += token
:QUOTED_FIELD
end
when :FIELD
case token
when @newline
got_field
got_row
:BEFORE_FIELD
when @separator
got_field
:BEFORE_FIELD
else
@field += token
:FIELD
end
when :BEFORE_FIELD
case token
when @separator
got_field
:BEFORE_FIELD
when @quote
:QUOTED_FIELD
when @newline
got_field
got_row
:BEFORE_FIELD
else
@field += token
:FIELD
end
when :MAYBE_ESCAPED_QUOTE
if token == @quote
@field += @quote
:QUOTED_FIELD
elsif @last_escape_used == @quote
@state = :BEFORE_SEPARATOR
consume token
else
@field += @last_escape_used
@field += token
:QUOTED_FIELD
end
when :BEFORE_SEPARATOR
case token
when @separator
got_field
:BEFORE_FIELD
when @newline
got_field
got_row
:BEFORE_FIELD
else
raise "Error: Separator or newline expected! Got: #{token} at (#{@line}:#{@column})"
end
end
if token == @newline
@column = 1
@line += 1
else
@column += token.length
end
#puts "[#{@line}:#{@column} - #{token}] Switched to #{@state}"
#if token == @quote then exit end
@state
end
プロファイリングの出力は次のとおりです。
さらに、Self部分は総実行時間の62%にもなるため、実際にはconsume
関数自体が遅く、呼び出す関数も少なくありません。
#consume関数で行われることの詳細は次のとおりです。
私はそれが原因かもしれないと思ったcase @state
ので、私が最初にしたことは、最も頻繁なcase
sを一番上に置くことでした(そして私はベンチマークしました:変化なし)。コードは私にはかなりきれいに見え、どこで多くを得ることができるかは実際にはわかりませんが、それでも標準のライブラリよりもはるかに遅いのは奇妙だと思います。
ちなみに、これをテストしているファイルは2MBのCSVファイルです。私はそれを一行ずつ読み、何もメモリに保存しません。消費関数を何もしない関数に置き換えると、Rubyの標準CSVと同じ速度が得られます(もちろん、何もしません:))ので、ボトルネックはI/Oにないと結論付けることができます。