After reading this thread on ruby-talk, I reviewed
my instance_exec implementation and found it totally
unacceptable. I used #undef_method instead of #remove_method, what a fool I
was! I'll first show why it can be a problem and then explain the causes; the problem is quite general, affecting a number of meta-hacks.
Take this script that just defines lots of methods to undefine them right
away, measuring the VmSize as it goes*1
ITER=10def vm_sizeFile.read("/proc/#{Process.pid}/status")[/^VmSize:\s+(\d+) kB/,1].to_iendvm1=vm_sizeputs"I'm #{Process.pid}, using #{vm1} kB."b=lambda{}ITER.timesdo|i|(i*10000...(i+1)*10000).eachdo|i|name="foo%6d"%iObject.module_eval{define_method(name,&b);undef_methodname}endGC.startendinc=vm_size-vm1puts"undef_method"puts"Increment: #{inc}, #{inc * 1024 / (ITER * 10000)} bytes per method."
Here's the output:
$ ruby undef_method.rb
I'm 1343, using 3020 kB.
undef_method
Increment: 23092, 118 bytes per method.
The script is saying that each method definition is taking around 120 bytes,
even when the method is removed right away.*2
This is not surprising:
the memleak is caused by the symbols. But
that's not all. I claimed that #undef_method was worse than #remove_method,
and here's the proof: running the above script after substituting
undef_method with remove_method yields this:
$ ruby remove_method.rb
I'm 3170, using 3020 kB.
remove_method
Increment: 6932, 70 bytes per method.
The increment per method remains consistently at least 40+ bytes smaller than
for #undef_method, so there's something besides symbol leaking.
How undef_method works
The difference lies in how remove_method and undef_method works.