为什么RuboCop建议用Array.new?

时间:2017-01-07 07:14:55

标签: ruby performance rubocop

RuboCop建议:

  

使用Array.new代替.times.map.

警察的docs

  

此警察检查.times.map调用。在大多数情况下,可以使用显式数组创建来替换此类调用。

示例:

# bad
9.times.map do |i|
  i.to_s
end

# good
Array.new(9) do |i|
  i.to_s
end

我知道它可以被替换,但我觉得9.times.map更接近英语语法,并且更容易理解代码的作用。

为什么要更换?

3 个答案:

答案 0 :(得分:28)

最后一个是更高效的,这是一个解释:Pull request where this cop was added

  

它检查这样的呼叫:

9.times.map { |i| f(i) }
9.times.collect(&foo)
     

并建议改用:

Array.new(9) { |i| f(i) }
Array.new(9, &foo)
     

新代码的大小大致相同,但使用的方法较少   在我看来,调用,消耗更少的内存,工作速度更快   更具可读性。

     

我见过很多次。{map,collect}在不同的时间   众所周知的项目:Rails,GitLab,Rubocop和几个闭源   应用

     

基准:

Benchmark.ips do |x|
  x.report('times.map') { 5.times.map{} }
  x.report('Array.new') { Array.new(5){} }
  x.compare!
end
__END__
Calculating -------------------------------------
           times.map    21.188k i/100ms
           Array.new    30.449k i/100ms
-------------------------------------------------
           times.map    311.613k (± 3.5%) i/s -      1.568M
           Array.new    590.374k (± 1.2%) i/s -      2.954M

Comparison:
           Array.new:   590373.6 i/s
           times.map:   311612.8 i/s - 1.89x slower
     

我现在还不确定Lint是警察的正确名称空间。让   我知道我是否应该将其转移到Performance。

     

此外,我没有实现自动更正,因为它可能   破坏现有代码,例如如果有人重新定义了Fixnum#times方法   做一些奇特的事。应用自动更正会破坏他们的代码。

答案 1 :(得分:11)

如果您觉得它更具可读性,请使用它。

这是一个性能规则,应用程序中的大多数代码路径可能都不是性能关键。就个人而言,我总是愿意接受过早优化的可读性。

那说

100.times.map { ... }
  • times创建了一个Enumerator对象
  • map枚举该对象而无法进行优化,例如,数据的大小不是预先知道的,它可能必须动态地重新分配更多空间,并且必须通过调用{{{}来枚举值。 1}}因为Enumerable#each以这种方式实现

尽管

map
  • Array.new(100) { ... } 分配一个大小为new
  • 的数组
  • 然后使用原生循环填写值

答案 2 :(得分:4)

当您需要映射调用固定次数的块的结果时,您可以选择:

Array.new(n) { ... }

n.times.map { ... }

后者对n = 10的速度慢约60%,n > 1_000的速度降至约40%。

注意:对数刻度!

enter image description here