我正在用 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
这是分析输出:
此外,真正consume
慢的是函数本身,而不是它调用的少数函数,因为 Self 部分高达总运行时间的 62%!
#consume 函数中发生的事情的详细信息是:
我认为case @state
可能是罪魁祸首,所以我做的第一件事就是把最常见case
的 s 放在顶部(我做了基准测试:没有变化)。代码对我来说似乎很干净,我真的看不出我能在哪里获得太多,但我仍然觉得它比标准库慢得多,这很奇怪。
顺便说一下,我正在测试的文件是一个 2MB 的 CSV 文件。我逐行阅读它并在内存中没有存储任何内容。如果我用一个什么都不做的函数替换我的消耗函数,我会得到与 Ruby 标准 CSV 相同的速度(但当然它什么都不做 :))所以我想我可以得出结论,瓶颈不在 I/O 中。