知道如何在Ruby中优化这个关键循环吗?

时间:2013-01-13 13:20:57

标签: ruby optimization

我正在用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 profiling output

此外,实际上consume函数本身很慢而且它调用的函数很少,因为Self部分高达总运行时间的62%!

#consume函数中发生的事情的细节是:

KCacheGrind profiling details

我认为case @state可能是罪魁祸首所以我做的第一件事就是把最常见的case放在最顶层(我做了基准测试:没有变化)。代码对我来说似乎很干净,我并没有真正看到我可以获得多少收益,但我发现它比标准的lib慢得多,这很奇怪。

顺便说一句,我正在测试它的文件是一个2MB的CSV文件。我一行一行地阅读它并且不在内存中存储任何内容。如果我用一个无效的函数替换我的消费函数,我获得与Ruby标准CSV相同的速度(当然它什么都不做:))所以我想我可以得出结论,瓶颈不在I / O中。

1 个答案:

答案 0 :(得分:0)

两点:

  • 您似乎有很多some_array.include?(something)。这很慢。尝试用这样的哈希替换它:

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

    并像some_hash[something]一样使用它。

  • 您似乎有很多some_string += another_string。这很慢。请尝试改为:

    some_string << another_string