3

我正在用 Ruby 编写一个 CSV 库(我知道,标准库很棒!)主要是为了好玩。目前它比标准慢大约 4 倍,我觉得它很奇怪,因为以下原因:我查看了 stdlib 中的 csv.rb ,它使用正则表达式来拆分行,我希望它不会很快。在我的库中,我使用 DFA,所以我确信它会在 O(n) 时间内运行 - 我几乎没有回溯,只有当我回溯一次以适应异常情况时(转义字符 == 引用字符) 并且它只发生大约 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 分析输出

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

#consume 函数中发生的事情的详细信息是:

KCacheGrind 分析详细信息

我认为case @state可能是罪魁祸首,所以我做的第一件事就是把最常见case的 s 放在顶部(我做了基准测试:没有变化)。代码对我来说似乎很干净,我真的看不出我能在哪里获得太多,但我仍然觉得它比标准库慢得多,这很奇怪。

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

4

1 回答 1

0

两点:

  • 你好像有很多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 回答