[prev in list] [next in list] [prev in thread] [next in thread] 

List:       ruby-core
Subject:    [ruby-core:107363] [Ruby master Feature#18285] NoMethodError#message uses a lot of CPU/is really exp
From:       "Eregon (Benoit Daloze)" <noreply () ruby-lang ! org>
Date:       2022-01-30 12:45:30
Message-ID: redmine.journal-96264.20220130124529.16600 () ruby-lang ! org
[Download RAW message or body]

Issue #18285 has been updated by Eregon (Benoit Daloze).


I think Ruby users already know well what `#<MyObject:0xXXXXXXXXXXXXXXXX>` means, and \
using another naming is likely more confusing. Either way it will need some getting \
used to as it's new output, and then it will just be natural. There are already \
places using `rb_any_to_s()` (e.g., when inspect/to_s don't return a String, etc) and \
this would just be another.

That said, `for MyObject instance`/`for module Foo`/`for class Bar` seems fine and \
simple enough if that's deemed better. "Integer object" or so seems confusing, so the \
instance terminology seems better.

----------------------------------------
Feature #18285: NoMethodError#message uses a lot of CPU/is really expensive to call
https://bugs.ruby-lang.org/issues/18285#change-96264

* Author: ivoanjo (Ivo Anjo)
* Status: Open
* Priority: Normal
----------------------------------------
Hello there! I'm working at Datadog on the ddtrace gem -- \
https://github.com/DataDog/dd-trace-rb and we ran into this issue on one of our \
internal testing applications. I also blogged about this issue in \
<https://ivoanjo.me/blog/2021/11/01/nomethoderror-ruby-cost/>.

### Background

While testing an application that threw a lot of `NoMethodError`s in a Rails \
controller (this was used for validation), we discovered that service performance was \
very much impacted when we were logging these exceptions. While investigating with a \
profiler, the performance impact was caused by calls to `NoMethodError#message`, \
because this Rails controller had a quite complex `#inspect` method, that was getting \
called every time we tried to get the `#message` from the exception.

### How to reproduce

```ruby
require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'

  gem 'benchmark-ips'
end

puts RUBY_DESCRIPTION

class GemInformation
  # ...

  def get_no_method_error
    method_does_not_exist
  rescue => e
    e
  end

  def get_runtime_error
    raise 'Another Error'
  rescue => e
    e
  end

  def inspect # <-- expensive method gets called when calling NoMethodError#message
    Gem::Specification._all.inspect
  end
end

NO_METHOD_ERROR_INSTANCE = GemInformation.new.get_no_method_error
RUNTIME_ERROR_INSTANCE = GemInformation.new.get_runtime_error

Benchmark.ips do |x|
  x.config(:time => 5, :warmup => 2)

  x.report("no method error message cost") { NO_METHOD_ERROR_INSTANCE.message }
  x.report("runtime error message cost") { RUNTIME_ERROR_INSTANCE.message }

  x.compare!
end
```

### Expectation and result

Getting the `#message` from a `NoMethodError` should be no costly than getting it \
from any other exception.

In reality:

```
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]

no method error message cost
                        115.390  ( ± 1.7%) i/s -    580.000  in   5.027822s
runtime error message cost
                          6.938M ( ± 0.5%) i/s -     35.334M in   5.092617s

Comparison:
runtime error message cost:  6938381.6 i/s
no method error message cost:      115.4 i/s - 60130.02x  ( ± 0.00) slower
```

### Suggested solutions

1. Do not call `#inspect` on the object on which the method was not found (see \
<https://github.com/ruby/ruby/blob/e0915ba67964d843832148aeca29a1f8244ca7b1/error.c#L1962>)
 2. Cache result of calling `#message` after the first call. Ideally this should be \
done together with suggestion 1.




-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic