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). /🧵

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

0 reposts2 likes

_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

There's only one "call cache" per "call data", - when our program is compiled it can only produce "call data" structures for method calls it knows about ahead of time. "obj.my_method" gets an explicit "call cache". But for `obj.send(:my_method)`, only the "send" call gets a "call cache"! /6