2017-01-07 19 views
13

RuboCop gợi ý:Tại sao RuboCop đề xuất thay thế .times.map bằng Array.new?

Sử dụng Array.new với một khối thay vì .times.map.

Trong docs cho cảnh sát:

cảnh sát này kiểm tra cho các cuộc gọi .times.map. Trong hầu hết các trường hợp, các cuộc gọi như vậy có thể được thay thế bằng việc tạo mảng rõ ràng.

Ví dụ:

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

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

Tôi biết nó có thể được thay thế, nhưng tôi cảm thấy 9.times.map là gần gũi hơn với ngữ pháp tiếng Anh, và nó dễ dàng hơn để hiểu những gì mã lệnh thực hiện.

Tại sao nó phải được thay thế?

+3

Lưu ý rằng nó nằm trong nhóm "Hiệu suất" (như bạn có thể nhìn thấy từ liên kết của bạn), mà là một chút của một gợi ý. :-) – ruakh

+0

Tất cả cảnh sát hiệu suất được điều chỉnh từ: https://github.com/JuanitoFatas/fast-ruby :-) – Drenmi

Trả lời

11

cuối cùng là performant hơn, đây là một lời giải thích: Pull request where this cop was added

Nó kiểm tra các cuộc gọi như thế này:

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

và đề nghị sử dụng này để thay thế:

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

Các mã mới có cùng kích thước nhưng sử dụng ít phương thức hơn cuộc gọi, tiêu thụ ít bộ nhớ hơn, w orks một chút nhanh hơn và theo ý kiến ​​của tôi là dễ đọc hơn.

Tôi đã nhìn thấy nhiều lần xuất hiện. {Map, collect} trong các dự án khác nhau: Rails, GitLab, Rubocop và một số mã nguồn đóng ứng dụng.

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 

Tôi không chắc bây giờ mà Lint là không gian tên chính xác cho cảnh sát. Hãy để tôi biết nếu tôi nên chuyển nó đến Hiệu suất.

Ngoài ra, tôi không triển khai tự động sửa vì nó có khả năng có thể ngắt mã hiện tại, ví dụ: nếu ai đó đã Fixnum # lần phương pháp redefined để làm một cái gì đó ưa thích. Áp dụng autocorrection sẽ phá vỡ mã của họ.

+1

Vâng, điều đó thực sự có vẻ như là một ý tưởng hay. Nó phân bổ trước một mảng có kích thước chính xác. 'times' không cho phép các đầu như vậy lên đến' map' để nó phải đoán và có khả năng thay đổi kích thước. – tadman

+1

Vui lòng không đăng liên kết, tóm tắt nội dung được liên kết trong một đoạn văn. – akuhn

+0

Và liên kết thậm chí không giải thích tại sao nó lại chậm hơn. – akuhn

7

Nếu bạn cảm thấy dễ đọc hơn, hãy đi theo nó.

Đây là quy tắc hiệu suất và hầu hết các hệ thống mã hóa trong ứng dụng của bạn có lẽ không phải là hiệu suất quan trọng. Cá nhân, tôi luôn mở để ủng hộ khả năng đọc qua tối ưu hóa sớm.

Điều đó nói rằng

100.times.map { ... } 
  • times tạo ra một đối tượng Enumerator
  • map liệt kê trên đối tượng đó mà không thể để tối ưu hóa, ví dụ như kích thước của mảng không được biết đến trước và nó có thể phải phân bổ lại không gian động hơn và phải liệt kê các giá trị bằng cách gọi Enumerable#each từ map được triển khai theo cách đó

Trong khi

Array.new(100) { ... } 
  • new phân bổ một mảng kích thước N
  • Và sau đó sử dụng một vòng lặp mẹ đẻ để điền vào các giá trị
2

Khi bạn cần để lập bản đồ kết quả của một khối đã gọi số lần cố định, bạn có tùy chọn giữa:

Array.new(n) { ... } 

và:

n.times.map { ... } 

Người thứ hai là chậm hơn khoảng 60% cho n = 10, mà đi xuống khoảng 40% cho n > 1_000.

Lưu ý: thang đo loga!

enter image description here

Các vấn đề liên quan