Skip to content
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

Add Performance/Caller cop #4078

Merged
merged 2 commits into from
Apr 17, 2017
Merged

Conversation

alpaca-tc
Copy link
Contributor

@alpaca-tc alpaca-tc commented Feb 28, 2017

Example

# bad
caller.first

# good
caller(1..1).first

Benchmark

require 'benchmark'

MAX = 10_000

def record(count = 100, &block)
  count.zero? ? yield : record(count - 1, &block)
end

def report(benchmark, name, &block)
  benchmark.report(name) do
    record { MAX.times(&block) }
  end
end

Benchmark.bm do |x|
  report(x, 'caller.first')       { caller.first }
  report(x, 'caller(1..1).first') { caller(1, 1).first }
end

#=>
#        user     system      total        real
# caller.first  1.470000   0.020000   1.490000 (  1.515402)
# caller(1..1).first  0.030000   0.010000   0.040000 (  0.035801)

Before submitting the PR make sure the following are checked:

  • Wrote good commit messages.
  • Commit message starts with [Fix #issue-number] (if the related issue exists).
  • Used the same coding conventions as the rest of the project.
  • Feature branch is up-to-date with master (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • Added an entry to the Changelog if the new code introduces user-observable changes. See changelog entry format.
  • All tests are passing.
  • The new code doesn't generate RuboCop offenses.
  • The PR relates to only one subject with a clear title
    and description in grammatically correct, complete sentences.
  • Updated cop documentation with rake generate_cops_documentation (required only when you've added a new cop or changed the configuration/documentation of an existing cop).

@alpaca-tc alpaca-tc force-pushed the caller_locations branch 3 times, most recently from f21dbe2 to a16fe1c Compare February 28, 2017 11:35
@alpaca-tc alpaca-tc changed the title [WIP] Add Performance/CallerLocations cop Add Performance/CallerLocations cop Feb 28, 2017
@@ -1342,6 +1342,11 @@ Lint/Void:

#################### Performance ###########################

Performance/CallerLocations:
Description: >-
Use `caller_locations` instance of `caller`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean instead of? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooops... 😇

SCOPE_METHODS = [:first, :[]].freeze

def on_send(node)
add_offense(node, node.loc.selector, MSG) if unneeded_caller?(node)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a shortcut that allows you to do this:

add_offense(node, :selector)


it 'registers an offense when :[] is called on caller' do
inspect_source(cop, 'caller[0]')
expect(cop.messages).to eq([described_class::MSG])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would normally assert:

expect(cop.offenses.size).to eq(1)

Asserting messages is generally only done for cops that have messages that change depending on context. 🙂


require 'spec_helper'

fdescribe RuboCop::Cop::Performance::CallerLocations do
Copy link
Contributor

@sachin21 sachin21 Mar 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fdescribe

This look like filter option.
If are you using filter option in development, you have to remove after development.

@alpaca-tc alpaca-tc force-pushed the caller_locations branch 2 times, most recently from 567bc03 to 7f9969f Compare March 1, 2017 07:43
@bbatsov
Copy link
Collaborator

bbatsov commented Mar 2, 2017

Some more background on this would be useful as the replacement code seems way more complex than the straightforward "slow" code.

@alpaca-tc alpaca-tc changed the title Add Performance/CallerLocations cop Add Performance/Caller cop Mar 4, 2017
@alpaca-tc
Copy link
Contributor Author

@bbatsov I fixed to simply replace with caller(1..1) rather than complex caller_locations.

@dorian Thank you for reviewing 👍

Seems like it's fixable (lazy enumerator instead of Array, lazy hash, lazy array, etc) (function is vm_backtrace_to_ary).

hmm, could you tell me more details to what should i fix? 🤔

@bbatsov
Copy link
Collaborator

bbatsov commented Mar 4, 2017

Just two more things:

  • in the first commit message drop the final .
  • in the second commit message - replace to with with

@bbatsov
Copy link
Collaborator

bbatsov commented Mar 4, 2017

Btw, shouldn't this cop be more generic? If you do caller[n] it should be replaced with caller(n..n)? Seems a bit odd to optimize just for one case.

@bbatsov
Copy link
Collaborator

bbatsov commented Mar 14, 2017

Any progress here? If not - I'll be forced to close this due to lack of activity.

@alpaca-tc
Copy link
Contributor Author

alpaca-tc commented Mar 14, 2017

Sorry, I kept you waiting. I am trying to improve this pr 🙏

@rrosenblum
Copy link
Contributor

Just adding a bit more bench-marking information because I was curious.

[14] pry(main)> Benchmark.ips do |x|
[14] pry(main)*   x.report('caller.first') { caller.first }  
[14] pry(main)*   x.report('caller(1..1).first') { caller(1..1).first }  
[14] pry(main)*   x.compare!
[14] pry(main)* end  
Warming up --------------------------------------
        caller.first     2.314k i/100ms
  caller(1..1).first    36.661k i/100ms
Calculating -------------------------------------
        caller.first     24.241k (±13.5%) i/s -    120.328k in   5.055955s
  caller(1..1).first    490.166k (±16.5%) i/s -      2.383M in   5.028717s

Comparison:
  caller(1..1).first:   490166.1 i/s
        caller.first:    24240.7 i/s - 20.22x  slower

Most of the ideas for our performance cops came from fast-ruby. I didn't see this one listed. It might be nice to make a PR against their project as well.

@alpaca-tc alpaca-tc force-pushed the caller_locations branch 4 times, most recently from f278d42 to c6ff058 Compare March 20, 2017 11:02
@alpaca-tc
Copy link
Contributor Author

@bbatsov Fixed 👍

@sachin21
Copy link
Contributor

@alpaca-tc You have to rebase and resolve conflicts.

@bbatsov
Copy link
Collaborator

bbatsov commented Mar 23, 2017

The messages at least still mention 1 explicitly instead of being a generic index. Perhaps you forgot about them? I also don't see any tests for anything but 1.

arguments = node.receiver.arguments

arguments.empty? ||
(arguments.length == 1 && arguments[0].type == :int)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check arguments[0].int_type? instead of arguments[0].type == :int?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

@alpaca-tc alpaca-tc force-pushed the caller_locations branch 3 times, most recently from 8eae5ab to 1499416 Compare April 11, 2017 01:25
@bbatsov
Copy link
Collaborator

bbatsov commented Apr 13, 2017

You should rebase this on top of the current master.

# caller(n..n).first
# caller(1..1).first
class Caller < Cop
MSG = 'Use `caller(n..n)` instead of `caller`.'.freeze
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess instead of caller[n] would be more clear here.

@bbatsov bbatsov merged commit e4e720b into rubocop:master Apr 17, 2017
@alpaca-tc alpaca-tc deleted the caller_locations branch April 18, 2017 12:07
@aripollak
Copy link

@alpaca-tc how come this suggests 1..1 instead of 1, 1? Either one has the same number of characters, but the former doesn't also require creating a Range object.

@alpaca-tc
Copy link
Contributor Author

@aripollak yeah, it sounds nice

@rrosenblum
Copy link
Contributor

rrosenblum commented May 25, 2017

I too noticed that the message could use some clarification. "Use caller(n..n) instead of caller[n]" isn't fully accurate. Wouldn't the correction need to be caller(n..n)[n] or caller(0, n)[n]?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants