From: "byroot (Jean Boussier)" Date: 2021-11-03T11:23:04+00:00 Subject: [ruby-core:105916] [Ruby master Misc#18285] NoMethodError#message uses a lot of CPU/is really expensive to call Issue #18285 has been updated by byroot (Jean Boussier). > I'm actually talking about NoMethodError, not NameError, so I don't think this issue is caused by the did you mean gem behavior No the confusion is mine, I assumed it was a `did_you_mean` perf issue without fully digging into your repro because I ran into those previously. > Could we simplify NoMethodError to not include the #inspect? I'm personally not against it, e.g: ``` >> {foo: 42}.bar (irb):2:in `
': undefined method `bar' for {:foo=>42}:Hash (NoMethodError) ``` The content of the hash here isn't really helpful, in the context of a `NoMethodError` all I really care about is the type of the object, so why not. I might be missing some use cases though. > the previous limit was in place for about 24 years and it looks like nobody really complained that they couldn't see their #inspect on objects which had a complex one. Well I for one complained about that limit, hence why I opened the ticket. Also note that the limit would still have called `#inspect`, just not used the result, so unless I'm missing something, you could truncate `Error#message` yourself and have roughly the same performance. And in the context of a library reporting error, you might have to deal with arbitrary errors raised by the application code, so you can't assume all errors will have a reasonably small `message` anyway. ---------------------------------------- Misc #18285: NoMethodError#message uses a lot of CPU/is really expensive to call https://bugs.ruby-lang.org/issues/18285#change-94459 * 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 . ### 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 ) 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: