-
-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion from Performance/BlockGivenWithExplicitBlock is often slower #385
Comments
To be fair, the assumption is that with
But even then, the difference is too small to be worth in unless you enter the case where the
|
That's another point in favour of removing the linter, which tells you to replace |
ROFL, I just assumed it was advocating for So yeah, definite 👍 |
This cop flags methods where `&block` is passed to a method, which is a very common practice. There's also debate[1] as to whether specifying `&block` explicitly is better than implicitly accepting a block. [1] rubocop/rubocop-performance#385
Following this Cop's advice produces significantly worse results on Ruby < 3.2 if a method forwards checks whether a block was given and then just forwards it. Consider the following example where we may call one of the # frozen_string_literal: true
require 'bundler/inline'
gemfile(true) do
gem 'benchmark-ips'
end
def each
3.times { |i| yield i }
end
def outer1(&block)
return enum_for(__method__) unless block_given?
each(&block)
end
def outer2(&block)
return enum_for(__method__) unless block
each(&block)
end
Benchmark.ips do |x|
x.report('outer1') { outer1 { } }
x.report('outer2') { outer2 { } }
x.compare!
end With Ruby < 2.6 (I tested down to Ruby 2.3), both
With Ruby >= 2.6, < 3.2 however, the block is not strictly materialized as a Proc when forwarding anymore. Thus, when passing block,
Only from Ruby 3.2 on, this improved and there is no actual difference between both options anymore — until you unwittingly introduce another pattern which again materializes the block as indicated by @jhawthorn above.
Thus, at the very least you should restrict the cop to be only active in Ruby >= 3.2.0. Here, it doesn't actively cause harm at least, although it doesn't provide much benefit too. As such, I'd also be happy if the cop is fully disabled by default. |
I openend #466 to disable by default. I don't think there's value in trying to update it into something that may give worse results in the next ruby version yet again. There's been some great performance work in Ruby itself lately and I wouldn't be surprised if these difference versions continue to converge. With YJIT, benchmark-ips doesn't even show the difference anymore (except that the correction is even worse) |
[Fix #385] Disable `Performance/BlockGivenWithExplicitBlock` by default
Performance/BlockGivenWithExplicitBlock
provides bad guidance and should be removed. The benchmark which was given when it was introduced didn't test the case that a block was given, when that happensblock
is significantly slower than the other option.For Ruby 3.2+ I made
if block
faster, but prior to that the advice from this cop was always wrong. Even in 3.2+ I believe it's bad advice as there's only a very narrow usage for whichblock
is (very slightly) faster, and even in those cases it's likely a future refactor the user will accidentally introduce the slow case (ex. moving fromif block
toif block && something_else
)The text was updated successfully, but these errors were encountered: