3

私は主に楽しみのために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

プロファイリングの出力は次のとおりです。

KCacheGrindプロファイリング出力

さらに、Self部分は総実行時間の62%にもなるため、実際にはconsume関数自体が遅く、呼び出す関数も少なくありません。

#consume関数で行われることの詳細は次のとおりです。

KCacheGrindプロファイリングの詳細

私はそれが原因かもしれないと思ったcase @stateので、私が最初にしたことは、最も頻繁なcasesを一番上に置くことでした(そして私はベンチマークしました:変化なし)。コードは私にはかなりきれいに見え、どこで多くを得ることができるかは実際にはわかりませんが、それでも標準のライブラリよりもはるかに遅いのは奇妙だと思います。

ちなみに、これをテストしているファイルは2MBのCSVファイルです。私はそれを一行ずつ読み、何もメモリに保存しません。消費関数を何もしない関数に置き換えると、Rubyの標準CSVと同じ速度が得られます(もちろん、何もしません:))ので、ボトルネックはI/Oにないと結論付けることができます。

4

1 に答える 1

0

2つのポイント:

  • あなたはたくさん持っているようですsome_array.include?(something)。これは遅いです。次のようなハッシュに置き換えてみてください。

    some_hash = {this_key => true, that_key => true, ...}

    のように使用しsome_hash[something]ます。

  • あなたはたくさん持っているようですsome_string += another_string。これは遅いです。代わりに試してください:

    some_string << another_string

于 2013-01-13T13:32:46.867 に答える