From: "mame (Yusuke Endoh)" Date: 2021-11-18T14:29:20+00:00 Subject: [ruby-core:106142] [Ruby master Misc#18285] NoMethodError#message uses a lot of CPU/is really expensive to call Issue #18285 has been updated by mame (Yusuke Endoh). @matz and @naruse said let's try `any_to_s` approach after Ruby 3.1 is released, towards Ruby 3.2. We need to handle some basic classes specially rather than true/false/nil. @akr pointed showing `#` for a class is rather confusing. ``` class Foo privatee end #=> current: undefined local variable or method `privvate' for Foo:Class (NameError) #=> proposal: undefined local variable or method `privvate' for # (NameError) ``` Also, @nobu said `#` would be annoying. Maybe we need to use the new behavior actually for a while, feel how stressful it is, and adjust the representation. ---------------------------------------- Misc #18285: NoMethodError#message uses a lot of CPU/is really expensive to call https://bugs.ruby-lang.org/issues/18285#change-94740 * 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: