top | item 21232237

(no title)

andr | 6 years ago

Are there any conditions where the compiler can optimize away the call to objc_msgSend? Or is it always used for any call between 2 ObjC/Swift methods?

discuss

order

saagarjha|6 years ago

In general, no, as message sends are observable and swizzlable so they they must go through the bare minimum of objc_msgSend just to make sure that they can be called from the cache. The only exception to this that I know is the retain/release methods, which are rarely overridden and called extremely often, so they have code that doesn't go through the message sending machinery unless necessary (in the case of a custom implementation). And of course, pure Swift calls don’t use the Objective-C runtime at all so they can be optimized as usual.

mpweiher|6 years ago

No.

There is never a way for the compiler to do this safely and automatically.

That said, there are ways for the programmer to do this manually if they know that it is safe to do so.

It would be nice if the compiler would help in these situations.

gok|6 years ago

Certain method calls (retain, release...) get compiled to special C functions that bypass obj_msgSend if the receiver has the default implementation.

mojuba|6 years ago

Always wondered about this too, but there doesn't seem to be a way of knowing which implementation your code will end up calling in Objective-C. "Final" would be helpful but there's no "final" in the language.

gpderetta|6 years ago

Speculative inlining driven by heuristics or PGO should always be possible. Same thing is done for virtual functions in C++ (or any indirect call really).

vidarh|6 years ago

I don't know about Objective-C specifically, but generally one way to achieve this in most dynamic languages is a speed/memory tradeoff.

In my Ruby compiler project which has to deal with the same level of dynamic behaviour, I handle dynamic overriding of methods with C++-style vtables, which turns method calls into the equivalent of this C-ish pseudo-code:

   (* ob->vtable[some_method_offset])(args...)
Since Ruby classes can have a method_missing handling undefined methods, for any class that doesn't implement a given method, the vtable contains a pointer to a thunk that tweaks the stack to push the relevant method symbol as the first argument and then does the same as above with the method offset of method_missing.

Since Ruby classes can have methods overridden at any time, if class Bar inherits from class Foo, inherits from class Object, and I override a method in class Object, that again has previously been explicitly overridden in class Bar, this would happen:

    - Store pointer to method in Object in ptr.
    - Replace method pointer in Object's vtable.
    - Iterate over all direct sub-classes of Object (but here we only care about Foo)
    - Compare the same method offset against ptr. Since Foo has not overridden the method, it matches.
    - Replace the pointer in Foo, and iterate over all direct sub-classes of Foo (but here we only care about Bar)
    - Compare the method offset against ptr. Since Bar has overridden the method, it doesn't match, so leave it alone.
This means that as long as method overrides doesn't happen extremely frequently, the cost of method overrides is relatively low: iterate over all the descendant classes of the class you override a method in.

Method calls on the other hand are about as cheap as virtual method calls in C++, except when you hit method_missing where this approach gives you a very low extra overhead of tweaking the stack to add the symbol and jumping to the method_missing implementation.

This overall approach works for most dynamic languages. The caveat is memory - if you have an application with very large class hierarchies in a language where they are all singly rooted (as in Ruby where they all ultimately inherit from SimpleObject), each vtable will cost you at last pointer_size*global_number_of_method_names. In practice so far I've not seen all that many cases where this is a problem, and it's always possible for the compiler to set a roof above which it will resort to a slow send mechanism (because you'll need to support that anyway in any language that allows dynamic send mechanics; e.g. in Ruby you can always send a message to an object by a dynamically obtained symbol so you still need the equivalent of objc_msgSend as well).

A slightly cheaper approach in terms of memory was described by Michael Franz[1]. His approach was to group methods in interfaces, so instead of a vtable of method pointers, you had a vtable of pointers to interfaces with pointers to methods. You save memory as most classes would typically implement most or none of the methods of an interface; it provides potential namespacing of the methods if you want to do that, and you can cut memory further by re-using the same vtable for an interface until someone tries to override at least one method in it. The cost is one extra indirection at call-sites.

[1] Protocol Extension: A Technique for Structuring Large Extensible SoftwareSystems (1994) https://pdfs.semanticscholar.org/60d3/045d924dfb912e184014aa...

gok|6 years ago

Generally impractical for real-world ObjC, which has around 100,000 method names.

Apple ObjC used to have a simplified version of this which used a vtable for the most frequent 16 selectors. It wasn't considered profitable after sufficient optimizations on the hash-table based method cache.