【问题标题】:Why does RuboCop suggest replacing .times.map with Array.new?为什么 RuboCop 建议用 Array.new 替换 .times.map?
【发布时间】:2017-05-21 23:52:57
【问题描述】:

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更接近英文语法,更容易理解代码的作用。

为什么要更换?

【问题讨论】:

  • 请注意,它在“性能”组中(如您从链接中看到的那样),这是一个提示。 :-)
  • 所有表演警察改编自:github.com/JuanitoFatas/fast-ruby :-)

标签: ruby performance rubocop


【解决方案1】:

后者性能更高;这是一个解释: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 是否是 cop 的正确命名空间。让 我知道我是否应该将其移至性能。

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

【讨论】:

  • 是的,这实际上看起来是个好主意。它预先分配了一个正确大小的数组。 timesmap 之前没有给出这样的提示,因此它必须猜测并可能调整大小。
  • 请不要只发链接,把链接的内容总结成一段。
  • 而且链接甚至没有解释为什么它变慢了。
  • 2 年后,rubocop 可能已移至另一个 repo,链接可能已失效。我们都来这里是为了教育自己:不理解一切,甚至不试图理解任何事情是有区别的。
  • “遵循最佳实践并不意味着完全理解底层原因”
【解决方案2】:

如果您觉得它更具可读性,请继续阅读。

这是一条性能规则,您的应用程序中的大多数代码路径可能对性能并不重要。就个人而言,我总是愿意支持可读性而不是过早的优化。

说的是

100.times.map { ... }
  • times 创建一个 Enumerator 对象
  • map 在无法优化的情况下枚举该对象,例如,数组的大小是未知的,它可能必须动态重新分配更多空间,并且它必须通过调用 Enumerable#each 来枚举值,因为 map就是这样实现的

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

【讨论】:

  • “如果你觉得它更易读,那就去吧。” 谢谢!另外,如果您查看@Drenmi 的答案,他会发现times 仅比Array 慢60%。如果它慢了 1000%,那么它可能会影响你的决定。但是这些细微的性能差异会被长期的人类可读性所抵消。
【解决方案3】:

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

Array.new(n) { ... }

和:

n.times.map { ... }

对于n = 10,后者慢了大约 60%,对于 n > 1_000,它下降到大约 40%。

注意:对数刻度!

【讨论】:

  • 感谢您添加性能差异并包含精美的日志图!
猜你喜欢
  • 2014-04-20
  • 2011-12-05
  • 2019-06-24
  • 1970-01-01
  • 1970-01-01
  • 2019-02-22
相关资源
最近更新 更多