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

Optimize auto_notify boolean check #774

Merged

Conversation

sambostock
Copy link
Contributor

Goal

This attempts to optimize a check that happens every time Bugsnag.notify is called.

The current implementation checks if auto_notify is an instance of TrueClass or FalseClass (in that order). As this is a "hot path" as far as Bugsnag is concerned (runs on all notifications), we can consider applying the following optimizations:

  • true and false are singleton instances of TrueClass and FalseClass, respectively. Therefore, we can rely on the slightly faster .equal? check, which relies on object identity.
  • false is the default for the method, so is likely the most common usage, so we can invert the order of the check and check for false before true, to benefit from short circuiting in the more common case.

Design

This new implementation is faster in the false and non-boolean cases, and while it is slower in the true case (because of the order inversion), it is still faster than the previous approach was in the false case.

It is safe because true and false are singleton instances of TrueClass and FalseClass, respectively.

The order inversion is based on the assumption that notify is usually called with auto_notify = false, as that is the default.

Notably, the new implementation does not harm readability, and is arguably clearer.

require "benchmark/ips"
[
  false, # Default    – Probably most common case
  true,  # Override   – Probably accounts for almost all other usage
  {},    # Deprecated – Probably barely any usage
].freeze.each do |auto_notify|
  Benchmark.ips do |x|
    x.compare!
    x.report("#{auto_notify}.is_a? TrueClass or #{auto_notify}.is_a? FalseClass (status quo)") do
      auto_notify.is_a? TrueClass or auto_notify.is_a? FalseClass
    end
    x.report("false.equal? #{auto_notify} or true.equal? #{auto_notify}         (proposed)  ") do
      false.equal? auto_notify or true.equal? auto_notify
    end
  end
end

produces the following results:

Implementation false true {}
auto_notify.is_a? TrueClass or auto_notify.is_a? FalseClass (status quo) 11.978M (± 0.6%) i/s 17.122M (± 1.3%) i/s 11.814M (± 0.5%) i/s
false.equal? auto_notify or true.equal? auto_notify (proposal) 17.955M (± 0.4%) i/s 13.553M (± 1.6%) i/s 13.420M (± 2.5%) i/s

Changeset

The auto_notify boolean check changes in the following ways:

  • .equal? is used instead of is_a?
  • The order of the check is inverted (now false is checked before true)

Testing

None of the behavior should have changed, so this does not introduce any new tests. A benchmark was used to see the difference.

The current implementation checks if `auto_notify` is an instance of
`TrueClass` or `FalseClass` (in that order). As this is a "hot path" as
far as Bugsnag is concerned (runs on all notifications), we can consider
applying the following optimizations:

- `true` and `false` are singleton instances of `TrueClass` and
  `FalseClass`, respectively. Therefore, we can rely on the slightly
  faster `.equal?` check, which relies on object identity.
- `false` is the default for the method, so is likely the most common
  usage, so we can invert the order of the check and check for `false`
  before `true`, to benefit from short circuiting in the more common case.

This new implementation is faster in the `false` and non-boolean cases,
and while it is slower in the `true` case (because of the order
inversion), it is still faster than the previous approach was in the
`false` case.

We can test this using the following benchmark:

    require "benchmark/ips"
    [
      false, # Default    – Probably most common case
      true,  # Override   – Probably accounts for almost all other usage
      {},    # Deprecated – Probably barely any usage
    ].freeze.each do |auto_notify|
      Benchmark.ips do |x|
        x.compare!
        x.report("#{auto_notify}.is_a? TrueClass or #{auto_notify}.is_a? FalseClass (status quo)") do
          auto_notify.is_a? TrueClass or auto_notify.is_a? FalseClass
        end
        x.report("false.equal? #{auto_notify} or true.equal? #{auto_notify}         (proposed)  ") do
          false.equal? auto_notify or true.equal? auto_notify
        end
      end
    end

which produces the following results:

    Implementation                                                              |`false`             |`true`              |`{}`
    ----------------------------------------------------------------------------|--------------------|--------------------|--------------------
    `auto_notify.is_a? TrueClass or auto_notify.is_a? FalseClass` _(status quo)_|11.978M (± 0.6%) i/s|17.122M (± 1.3%) i/s|11.814M (± 0.5%) i/s
    `false.equal? auto_notify or true.equal? auto_notify`         _(proposal)_  |17.955M (± 0.4%) i/s|13.553M (± 1.6%) i/s|13.420M (± 2.5%) i/s
@johnkiely1
Copy link
Member

Hey @sambostock, thanks for the PR. We will review and merge as soon as priorities allow.

imjoehaines
imjoehaines previously approved these changes Feb 1, 2023
Copy link
Contributor

@imjoehaines imjoehaines left a comment

Choose a reason for hiding this comment

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

Thanks @sambostock!

This is a great PR description ⇔ code change ratio 😀

@imjoehaines imjoehaines changed the base branch from master to next February 1, 2023 09:49
@imjoehaines imjoehaines dismissed their stale review February 1, 2023 09:49

The base branch was changed.

@imjoehaines imjoehaines merged commit 88d0c87 into bugsnag:next Feb 1, 2023
@imjoehaines imjoehaines mentioned this pull request Feb 1, 2023
@johnkiely1
Copy link
Member

johnkiely1 commented Feb 16, 2023

This has been released in v6.25.2

@johnkiely1 johnkiely1 added the released This feature/bug fix has been released label Feb 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
released This feature/bug fix has been released
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants