ruby - 优化用于将字符串解析为数值的 ruby​​ 代码

标签 ruby testing optimization speed-test

我目前正在为 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/

相关文章:

c++ - 我怎样才能在 CUDA 中比较很多 vector (有效地)

ruby-on-rails - #<ProjectsController :0x007faead1853e0> 的未定义方法 `user_signed_in?'

ruby-on-rails - 我的 Rails 文件的 Nginx 权限被拒绝错误?

ruby - 如何有效地加入从 json 列表收到的 ruby​​ 散列

python - Turbogears2 和 py.test

java - 无法单击从工厂方法传递的元素

javascript - 通过测试咖啡馆上传文件会导致 Fine Uploader 库出现错误

Python 优化程度(公共(public)子表达式消除)

ruby - 有没有办法初始化哈希的所有子节点?

mysql - 查询优化器出现奇怪的 mysql 问题