It's amazing that such a performance-critical part of Objective C's runtime is using a per-class cache for message sends - the same level of technology that powered Ruby 1.8 if you can relate to that!
I wonder if you could do some very limited dynamic code generation to create inline caches, or just inline msgSend and allocate a cache line somewhere writable for each inlined instance.
Or is this method already a slow-path, fallback case?
Dynamic code generation at the call site means you can no longer share any code pages which contain message sends, so memory utilization goes way up unless you restrict this to app binaries and not libraries. But then most of your time is going to be spent in libraries, so the benefit will be much smaller.
Then there's a question of how much space you reserve for the dynamic code. Since the rest of the code is built statically you can't really shuffle things around, and have to have a hole where the code can be inserted. Make the hole too big, and you're wasting precious I-cache space. Make it too small and you're screwed. But you can't predict the future very easily to know how big the message send code might be three OS revisions down the line.
On iOS you can't write code dynamically at all in a normal process, so this all goes out the window. Your only choice there would be to generate inlining logic at compile time, and that has dire problems for binary compatibility.
objc_msgSend is pretty fast. I believe it's currently around high single-digit CPU cycles per call on the fast path.
Doing a PIC on precompiled code is tricky, especially when you want to enforce a security model.
Global caches perform reasonably well for many cases, and if you can exclude the long tail and keep the common cases then you'll get okay performance. Combine that with static analysts to deal with the cases you can resolve at compile time and you do okay.
I'm still a fan of PICs when used with a JIT, but I'm not sure they are always the right answer.
Any idea why it's no longer in use? The memory argument against vtables seems spurious - I'm using vtables everywhere for my in-progress Ruby compiler (with the intent of allowing an arbitrary cut-off on the vtables and spilling the excess messages into hash tables at some point), and as far as I've seen, it takes some pretty pathological cases (e.g. an app with an unusually huge amount of classes and methods that consists of little actual code, yet relatively small number of actual object instances allocated) before the memory waste for sparse vtables start becoming anything remotely significant relative to an applications other memory usage or code size.
You know, when I tell people that Obj-C's message dispatch makes it a bit trickier to do a lot of the optimizations you find in C++ compilers, they always look at me like I'm crazy, but this seems like very clear evidence of this...
Why would they think you're crazy? It should be quite obvious that an Objective-C compiler simply cannot optimize across the message send boundary in any way, while a C++ compiler often can optimize across method calls.
Because despite pointing out exactly why I thought it was true and explaining Obj-C's various dispatch optimization tricks and how they still left an optimizing compiler/runtime with a far greater challenge, the response was generally, "you have no idea what you are talking about".
Thanks to Dunning-Kruger, it's quite difficult to be sure if they just can't be bothered to explain what I don't understand (maybe performance differences arise elsewhere... maybe there were far more sophisticated tricks in the modern message dispatch code than I'd come to understand... though it seemed unlikely they'd be so significant) or its the other way around.
Empirical evidence would seem to point strongly in my favour, but you'd think that'd give people some pause before making such statements.
Unfortunately, the nature of Objective-C as an Apple-centric language means a lot of people like it because Apple says they're supposed to, and don't take criticism of it well.
objc_msgSend is pretty amazingly fast, and is not usually a bottleneck in code, but the inability to do things like inline method calls when the target is known at compile time is a downer.
I wonder if you could do some very limited dynamic code generation to create inline caches, or just inline msgSend and allocate a cache line somewhere writable for each inlined instance.
Or is this method already a slow-path, fallback case?