Почему RuboCop предлагает заменить файл с расширением Array.new?

RuboCop предлагает:

Используйте Array.new с блоком вместо .times.map.

В документах для полицейского:

Этот полицейский проверяет вызовы .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 ближе к грамматике английского языка, и легче понять, что делает код.

Почему это должно быть заменено?

Ответ 1

Последний более производительный; Вот объяснение: Запрос на извлечение, где был добавлен этот полицейский

Он проверяет такие звонки следующим образом:

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

и предлагает использовать вместо этого:

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

Новый код имеет примерно такой же размер, но использует меньше методов звонки, потребляет меньше памяти, работает чуть быстрее и на мой взгляд более читабелен.

Я видел много случаев раз. {Карта, собирать} в разных известные проекты: Rails, GitLab, Rubocop и несколько закрытых источников приложения.

Benchmarks:

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

Теперь я не уверен, что Линт - это правильное пространство имен для полицейского. Позволять я знаю, если я должен переместить его в Performance.

Также я не реализовал автокоррекцию, потому что она может потенциально сломать существующий код, например если кто-то переопределил метод Fixnum # times сделать что-то причудливое. Применение автокоррекции нарушит их код.

Ответ 2

Если вы чувствуете, что это более читабельно, идти с ним.

Это правило производительности, и большинство путей кода в вашем приложении, вероятно, не являются критичными для производительности. Лично я всегда открыт для предпочтения удобочитаемости по сравнению с преждевременной оптимизацией.

Что сказал

100.times.map { ... }
  • times создает объект Enumerator
  • map перечисляет этот объект без возможности оптимизации, например, размер массива не известен заранее, и может потребоваться динамическое перераспределение большего пространства, и он должен перечислять значения, вызывая Enumerable#each поскольку map реализована таким образом

В то время как

Array.new(100) { ... }
  • new выделяет массив размером N
  • А затем использует собственный цикл для заполнения значений

Ответ 3

Когда вам нужно отобразить результат блока, вызванного фиксированное количество раз, у вас есть выбор между:

Array.new(n) { ... }

и:

n.times.map { ... }

Последний вариант примерно на 60% медленнее для n = 10, который снижается примерно до 40% для n > 1_000.

Примечание: логарифмическая шкала!

enter image description here