Post

TIL: In Ruby, there is a significant performance difference between calling `obj.my_method` vs calling `obj.send(:my_method)`. To be clear, "Significant" in this instance is more the realm of microbenchmarks, or very hot code path optimizations (hundreds of thousands/millions of calls). /🧵

2 reposts20 likes

Until more recently, my mental model was a call to `obj.my_method` was basically just a call to `obj.send(:my_method)` behind the scenes. Ruby is all about sending messages to objects, after all. What more explicit way of "send"ing a message was there than to call "send"?! /2

_Semantically_, I think this is still the correct mental model. One obvious difference is that `send` bypasses visibility modifier restrictions, but you can (and should) call `public_send` to achieve the same behavior. But for _performance_, `send` will pretty much always be slower. /3

The reason `obj.my_method` is so much faster, is because of inline caching. I learned about this from byroots JSON optimization series, and from my own work to add a new instruction to the VM. I thought the compiler would do some magic and make them equivalent. /4

But the inline caches only improve the performance of explicit method calls. Direct method calls are related to "calldata". This "calldata" maps directly to a struct in CRuby called "rb_call_data", which contains information about the call, and a "call cache". /5

Awesome, thanks for the correction! And wow, `public_send`, ouch. Those numbers are rough. I noticed that `send` seems to have a specialized version and `public_send` doesn't, must be related to that somehow?

Oh yea I noticed your note - # YJIT has an optimization for send but not public_send Would be nice to optimize public_send - that's usually my recommendation if I see people using send.