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

你知道如何在Ruby中优化这个关键循环吗?,ruby,optimization,Ruby,Optimization,我正在用Ruby编写一个CSV库(我知道,标准库很棒!),主要是为了好玩。目前,它比标准慢4倍左右,我觉得这很奇怪,因为以下几点:我已经查看了stdlib中的csv.rb,它使用正则表达式来分割行,我预计不会很快。在我的库中,我使用DFA,因此我确信它将在O(n)时间内运行-我几乎没有回溯,只有在回溯一次以适应异常情况(escape char==quote char)时才会出现,并且只发生大约1%的时间 所以我分析了我的代码,这里是占总运行时间89%的部分。它是为输入文件的每个字符运行的循环:

我正在用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
以下是分析输出:

此外,真正缓慢的是
消费
函数本身,而不是它调用的少数函数,因为自身部分高达总运行时间的62%

#消费函数中发生的详细信息如下:

我认为
case@state
可能是罪魁祸首,所以我做的第一件事就是把最频繁的
case
s放在首位(我的基准是:没有变化)。代码对我来说似乎很干净,我不知道我能从中获得多少,但我仍然觉得奇怪,它比标准库慢得多

顺便说一下,我正在测试的文件是一个2MB的CSV文件。我一行一行地读,却什么也没存储在记忆里。如果我用一个不做任何事情的函数替换我的消费函数,我会得到与Ruby标准CSV相同的速度(但当然它什么也不做:),因此我认为我可以得出结论,瓶颈不在I/O中。

两点:

  • 你似乎有很多
    数组。包括?(某物)
    。这太慢了。尝试用如下所示的哈希值替换它:

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

    然后像
    some\u hash[something]
    一样使用它

  • 你似乎有很多
    一些字符串+=另一个字符串
    。这太慢了。请尝试:


    与标准库相比,您的方法是否使用了大量的if/else或其他分支语句?正则表达式可能看起来很慢,但阻止CPU使用分支预测可能是一个相当负面的影响。大多数我粘贴在这里的代码中,有很多但没有什么奢侈的东西,我认为对于非病理正则表达式,我希望正则表达式引擎速度非常快,因为它们是本机的,并且经过了反复调整。注意,许多使用DFA和本地DFA。你可能会感兴趣,也许在它引用的三篇文章中会更感兴趣:有趣的东西(对于“有趣”这个更古怪的值),这个方法太大了。为了理智起见,将其拆分为更小、包含良好的方法。这可能会使事情稍微慢一点(由于函数调用的开销),但它使代码更容易理解。正确编写的正则表达式速度非常快。我已经做了很多基准测试,所以将它们与其他算法进行比较,除了非常具体的用途外,编写良好的正则表达式将获胜。这就是说,一个写得不好的文件除了很难维护外,还会严重浪费CPU时间