Bug #11119
closedAnonymous classes and modules have terrible #name and #inspect performance
Description
MRI lazily determines the name of a class or module by walking all defined constants starting from Object
and looking for the namespace in question. This allows deferring the full name calclation until the class/module is finished being defined. However, if the class or module is never accessible via Object
, then this system-walking occurs for every call to #name
or #inspect
on the class
/module
and every call to the default #inspect
on instances of the class.
A simple benchmark:
require 'benchmark'
module B
module X
end
end
def a
c = Class.new
c2 = Class.new
c.class_eval 'A = c2'
c2.class_eval 'A = c'
c
end
c = a
x = B::X
loop do
puts 'named'
puts Benchmark.measure { 1_000_000.times { x.name } }
puts 'anon'
puts Benchmark.measure { 1_000_000.times { c.name } }
cobj = c.new
puts 'anon obj'
puts Benchmark.measure { 1_000_000.times { cobj.inspect } }
end
Results on MRI 2.2 and JRuby 1.7 HEAD:
MRI:
named
0.210000 0.000000 0.210000 ( 0.205585)
anon
14.170000 0.050000 14.220000 ( 14.259003)
anon obj
15.750000 0.060000 15.810000 ( 15.864806)
JRuby:
named
0.250000 0.000000 0.250000 ( 0.253000)
anon
0.270000 0.000000 0.270000 ( 0.264000)
anon obj
0.450000 0.000000 0.450000 ( 0.447000)
The effect worsens linearly with the size of the system. Running in a freshly-generated Rails app's console:
named
0.260000 0.020000 0.280000 ( 0.272182)
anon
240.900000 0.800000 241.700000 (242.384455)
anon obj
257.070000 1.110000 258.180000 (261.986562)
I believe MRI needs to give up on looking for the object after the first failed namespace traversal, or else eagerly build this name the way other implementations do (and accept some changes).