我目前正在为 ruby 进行一些速度测试,我需要将一些文本文件解析为数值。由于速度慢,我想知道我的代码是否可以优化,或者 ruby 是否真的那么慢。 正在从文件中读取代码,这些文件包含大约 1 000 000 行随机生成的行或数字,我只会显示几行,以便您知道正在读取的内容。 我需要读取的文件名作为参数传递,coed 是单独的脚本(只是为了我自己清楚)。
首先我想解析一个简单的数字,输入格式如下:
type
number
type
number
...
我是这样做的:
incr = 1
File.open(ARGV[0], "r").each_line do |line|
incr += 1
if incr % 3 == 0
line.to_i
end
end
其次,我需要解析为单个列表,输入采用以下格式:
type
(1,2,3,...)
type
(1,2,3,...)
...
我是这样做的
incr = 1
File.open(ARGV[0], "r").each_line do |line|
incr += 1
if incr % 3 == 0
line.gsub("(","").gsub(")","").split(",").map{ |s| s.to_i}
end
end
最后我需要解析成列表的列表,输入采用这种格式:
type
((1,2,3,...),(1,2,3,...),(...))
type
((1,2,3,...),(1,2,3,...),(...))
...
我是这样做的:
incr = 1
File.open(ARGV[0], "r").each_line do |line|
incr += 1
if incr % 3 == 0
line.split("),(").map{ |s| s.gsub("(","").gsub(")","").split(",").map{ |s| s.to_i}}
end
end
我不需要显示任何结果,我只是进行速度测试,所以不需要输出。我确实检查了结果,代码本身似乎工作正常,它们只是出奇地慢,我想用 ruby 提供的最佳功能进行速度测试。 我知道有几个我可以使用的速度测试,但为了我的目的,我需要建立我自己的。
我可以做些什么更好?如何优化这段代码?我哪里出错了,或者这已经是 ruby 所能做到的最好的了? 提前感谢您的提示和想法。
最佳答案
在第一个中,而不是:
File.open(ARGV[0], "r").each_line do |line|
使用:
File.foreach(ARGV[0]) do |line|
而不是:
incr += 1
if incr % 3 == 0
使用:
if $. % 3 == 0
$.
是最后读取行的行号的魔法变量。
在第二个中,而不是:
line.gsub("(","").gsub(")","").split(",").map{ |s| s.to_i}
使用:
line.tr('()', '').split(',').map(&:to_i)
在第三个,而不是:
line.split("),(").map{ |s| s.gsub("(","").gsub(")","").split(",").map{ |s| s.to_i}}
使用:
line.scan(/(?:\d+,?)+/).map{ |s| s.split(',', 0).map(&:to_i) }
这条线是这样工作的:
line.scan(/(?:\d+,?)+/)
=> ["1,2,3,", "1,2,3,"]
line.scan(/(?:\d+,?)+/).map{ |s| s.split(',',0) }
=> [["1", "2", "3"], ["1", "2", "3"]]
line.scan(/(?:\d+,?)+/).map{ |s| s.split(',', 0).map(&:to_i) }
=> [[1, 2, 3], [1, 2, 3]]
我没有运行任何基准测试来比较速度,但变化应该也更快,因为 gsub
调用已经消失。我所做的更改不一定是最快的做事方式,它们是您自己代码的更优化版本。
尝试将 Ruby 的速度与其他语言进行比较需要了解完成每个步骤的最快方法,并基于该步骤的多个基准。它还意味着您在相同的硬件和操作系统上运行,并且您的语言都被编译为最高效的速度形式。语言会在内存使用与速度之间进行权衡,因此,虽然一种语言可能比另一种慢,但它的内存效率也可能更高。
此外,在生产环境中编码时,生成正确运行的代码的时间必须计入“哪个更快”的等式中。 C 非常快,但对于大多数问题,编写程序比 Ruby 花费的时间更长,因为 C 不像 Ruby 那样握住你的手。 C 代码需要一周的时间来编写和调试,而 Ruby 代码需要一个小时,哪个更快?只是需要考虑的事情。
在我读完之前,我没有通读@tadman 的回答和评论。使用:
map(&:to_i)
过去比:
map{ |s| s.to_i }
速度差异取决于您运行的 Ruby 版本。最初使用 &:
是在一些猴子补丁中实现的,但现在它已内置到 Ruby 中。当他们进行更改时,速度大大加快:
require 'benchmark'
foo = [*('1'..'1000')] * 1000
puts foo.size
N = 10
puts "N=#{N}"
puts RUBY_VERSION
puts
Benchmark.bm(6) do |x|
x.report('&:to_i') { N.times { foo.map(&:to_i) }}
x.report('to_i') { N.times { foo.map{ |s| s.to_i } }}
end
哪些输出:
1000000
N=10
2.0.0
user system total real
&:to_i 1.240000 0.000000 1.240000 ( 1.250948)
to_i 1.400000 0.000000 1.400000 ( 1.410763)
这要经过 10,000,000 个元素,这只会导致 0.2/秒的差异。做同一件事的两种方法之间没有太大区别。如果您要处理更多数据,那么它很重要。对于大多数应用程序来说,这是一个有争议的问题,因为其他因素会成为瓶颈/减速,因此请以适合您的方式编写代码,并牢记速度差异。
为了显示 Ruby 版本的不同,这里是使用 Ruby 1.8.7 的相同基准测试结果:
1000000 N=10 1.8.7 user system total real &:to_i 4.940000 0.000000 4.940000 ( 4.945604) to_i 2.390000 0.000000 2.390000 ( 2.396693)
As far as gsub
vs. tr
:
require 'benchmark'
foo = '()' * 500000
puts foo.size
N = 10
puts "N=#{N}"
puts RUBY_VERSION
puts
Benchmark.bm(6) do |x|
x.report('tr') { N.times { foo.tr('()', '') }}
x.report('gsub') { N.times { foo.gsub(/[()]/, '') }}
end
有了这些结果:
1000000 N=10 1.8.7 user system total real tr 0.010000 0.000000 0.010000 ( 0.011652) gsub 3.010000 0.000000 3.010000 ( 3.014059)
and:
1000000 N=10 2.0.0 user system total real tr 0.020000 0.000000 0.020000 ( 0.017230) gsub 1.900000 0.000000 1.900000 ( 1.904083)
Here's the sort of difference we can see from changing the regex pattern, which forces changes in the processing needed to get the desired result:
require 'benchmark'
line = '((1,2,3),(1,2,3))'
pattern1 = /\([\d,]+\)/
pattern2 = /\(([\d,]+)\)/
pattern3 = /\((?:\d+,?)+\)/
pattern4 = /\d(?:[\d,])+/
line.scan(pattern1) # => ["(1,2,3)", "(1,2,3)"]
line.scan(pattern2) # => [["1,2,3"], ["1,2,3"]]
line.scan(pattern3) # => ["(1,2,3)", "(1,2,3)"]
line.scan(pattern4) # => ["1,2,3", "1,2,3"]
line.scan(pattern1).map{ |s| s[1..-1].split(',').map(&:to_i) } # => [[1, 2, 3], [1, 2, 3]]
line.scan(pattern2).map{ |s| s[0].split(',').map(&:to_i) } # => [[1, 2, 3], [1, 2, 3]]
line.scan(pattern3).map{ |s| s[1..-1].split(',').map(&:to_i) } # => [[1, 2, 3], [1, 2, 3]]
line.scan(pattern4).map{ |s| s.split(',').map(&:to_i) } # => [[1, 2, 3], [1, 2, 3]]
N = 1000000
Benchmark.bm(8) do |x|
x.report('pattern1') { N.times { line.scan(pattern1).map{ |s| s[1..-1].split(',').map(&:to_i) } }}
x.report('pattern2') { N.times { line.scan(pattern2).map{ |s| s[0].split(',').map(&:to_i) } }}
x.report('pattern3') { N.times { line.scan(pattern3).map{ |s| s[1..-1].split(',').map(&:to_i) } }}
x.report('pattern4') { N.times { line.scan(pattern4).map{ |s| s.split(',').map(&:to_i) } }}
end
在 Ruby 2.0-p427 上:
user system total real
pattern1 5.610000 0.010000 5.620000 ( 5.606556)
pattern2 5.460000 0.000000 5.460000 ( 5.467228)
pattern3 5.730000 0.000000 5.730000 ( 5.731310)
pattern4 5.080000 0.010000 5.090000 ( 5.085965)
关于ruby - 优化用于将字符串解析为数值的 ruby 代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17860692/