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