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

Speed up #69

Merged
merged 1 commit into from
Nov 8, 2023
Merged

Speed up #69

merged 1 commit into from
Nov 8, 2023

Conversation

kddnewton
Copy link
Contributor

👋 Hi! I heard this could benefit from some improved performance, and also that it used ripper. I'm on a mission to remove ripper from everywhere. So, I took a look. I did a couple of things to improve performance, namely:

  • I swapped out ripper for prism. It requires less knowledge of the AST, is easier to maintain, and is faster if you're just parsing comments.
  • I changed Events from a module to a class. Previously, every event on every todo lined up to a method call to the Events module. That module would then instantiate whatever data it needed to do its job. But none of this data could be shared between todos. So it was discarded when the method call returned. Now instead we cache the data we want to keep around (like rubygems or github clients).
  • I inlined a couple of method calls, removed some dynamic dispatch, and made sure the code was a little more shape friendly by initializing instance variables in initializers.
  • I made the dispatches parallel so that when we're actually hitting slack it will switch threads to make multiple requests at once.

There are still quite a few improvements I think could be put in place, but this seemed like a good place to stop. Those would be:

  • Grouping events before they are checked, then checking them as a single corpus. For example, if you grouped all of the date events together, you could sort them and then find the index of the first one to match, then take the remaining. For github-related events, you could create a single HTTP connection instead of one per event.
  • Right now a different HTTP connection is made to slack on every dispatch.
  • Replace prism parsing the body of the todo with stringscanner. I imagine this would be faster, and you would get better error messages.

Overall, with these improvements in place, the time to parse Shopify's monolith on my computer went from 1:26.26 to 22.789:

CI=1 bin/profile ../shopify  65.70s user 2.71s system 79% cpu 1:26.26 total
CI=1 bin/profile ../shopify  3.86s user 2.30s system 27% cpu 22.789 total

A couple of things to note:

  • Right now it's pointing to a GitHub version of prism because I created a new API for this. I'll release the new version soon.
  • I removed the rexml dependency because it was never used.

Sorry about the size of this PR. I saw a bunch of stuff so I just kept going. Happy to trim this back to individual small commits if you'd prefer. I just figured I'd get this in front of eyes now and then we can reevaluate the scope of it.

Gemfile Outdated Show resolved Hide resolved
private

def now
@now ||= Time.now
Copy link
Member

Choose a reason for hiding this comment

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

Why aren't we just setting this on the initialize?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same answer - if you never get a on: date then we don't need to create this object.

Comment on lines 100 to 113
def rubygems_client
@rubygems_client ||= Net::HTTP.new("rubygems.org", Net::HTTP.https_default_port).tap do |client|
client.use_ssl = true
end
end

def github_client
@github_client ||= Net::HTTP.new("api.github.com", Net::HTTP.https_default_port).tap do |client|
client.use_ssl = true
end
end

def installed_ruby_version
@installed_ruby_version ||= Gem::Version.new(RUBY_VERSION)
Copy link
Member

Choose a reason for hiding this comment

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

Why aren't we setting those in the initialize?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're lazily setting these up because if you don't find any todos that have a gem version, there's no need to instantiate these objects. Basically it mirrors the previous behavior of lazily creating objects based on the todos you find.

# An event that check if the passed date is passed
class Date
class << self
# @param on_date [String] a string parsable by Time.parse
# @return [String, false]
def met?(on_date)
if Time.now >= Time.parse(on_date)
def met?(on_date, now = Time.now)
Copy link
Member

Choose a reason for hiding this comment

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

Do we even use this method without the second argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't, I just wasn't sure how much you wanted to break the existing APIs. Happy to make this required if you're prefer.

Copy link
Member

Choose a reason for hiding this comment

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

It is fine to break API, all those classes are private API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice, in this case I've inlined the classes.

@@ -17,16 +17,17 @@ class GemBump
#
# @example Expecting a version in the 5.x.x series
# GemBump.new('rails', ['> 5.2', '< 6'])
def initialize(gem_name, requirements)
def initialize(gem_name, requirements, spec_set = Bundler.load.specs)
Copy link
Member

Choose a reason for hiding this comment

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

Do we use this class without the last argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, removed the class wholesale.

@@ -18,9 +18,10 @@ class GemRelease
#
# @example Expecting a version in the 5.x.x series
# GemRelease.new('rails', ['> 5.2', '< 6'])
def initialize(gem_name, requirements)
def initialize(gem_name, requirements, client = nil)
Copy link
Member

Choose a reason for hiding this comment

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

Same here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here.

@@ -22,11 +22,12 @@ class IssueClose
# @param organization [String]
# @param repo [String]
# @param pr_number [String, Integer]
def initialize(organization, repo, pr_number, type:)
def initialize(organization, repo, pr_number, client = nil, type:)
Copy link
Member

Choose a reason for hiding this comment

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

Same here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here.

def initialize(requirements)
attr_reader :installed_ruby_version

def initialize(requirements, installed_ruby_version = Gem::Version.new(RUBY_VERSION))
Copy link
Member

Choose a reason for hiding this comment

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

Same here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here.

@kddnewton
Copy link
Contributor Author

Just one sec - I released the new version of Prism so I'll make it point to rubygems.org

@kddnewton
Copy link
Contributor Author

@rafaelfranca this is good to go now. Should I merge?

@rafaelfranca
Copy link
Member

Yes. :shipit:

@kddnewton kddnewton merged commit 6c07c84 into main Nov 8, 2023
6 checks passed
@kddnewton kddnewton deleted the speed branch November 8, 2023 22:16
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.

2 participants