Monday, August 18, 2014

“It’s a Coup”

I highly recommend the Edge Cases episode Swift Is a Really Good Thing and a Step Back. There’s much to like about Swift, but much has been lost as well: instantiating classes by name, the ability to write your own Core Data–like framework, message passing (contra vtable dispatch), swizzling to work around bugs and to add features, proxies/bridges, and more.

The quote in this post’s title, from Andrew Pontious, refers to the general lack of outrage over the loss of dynamism. In broad strokes, the C++ people have asserted their vision that the future will be static, and the reaction from the Objective-C crowd has been apathy. Apple hasn’t even really tried to make a case for why this U-turn is a good idea, and yet the majority seems to have rolled over and accepted it, anyway.

Since the episode was released, there’s been some good news: Wolf Rentzsch is not leaving Edge Cases, as was implied. More mixed is the news of the significant changes that Apple has made to Swift in beta 5.

The latest version of The Swift Programming Language discusses the new dynamic modifier:

Apply this modifier to any member of a class that can be represented by Objective-C. When you mark a member declaration with the dynamic modifier, access to that member is always dynamically dispatched using the Objective-C runtime. Access to that member is never inlined or devirtualized by the compiler.

Because declarations marked with the dynamic modifier are dispatched using the Objective-C runtime, they’re implicitly marked with the objc attribute.

Previously, there was only the @objc attribute, and this made it seem like message passing was deprecated. The only way to get it was via the Objective-C compatibility layer, and the @ sign emphasized that it was not part of the language proper.

One way to interpret the addition of the dynamic modifier, which lacks a @, is that Apple plans for message passing to be at least an optional part of Swift going forward. It’s currently implemented using the Objective-C runtime, but it’s no longer tied to @objc. In a future release, there could be pure Swift classes that use message passing.

However, the other half of this change is that methods in Objective-C classes are no longer dynamic by default. The dynamic modifier doesn’t actually make things more dynamic; rather, Apple has made everything more static but allowed you to opt out by annotating individual methods. To define an Objective-C method, you used to type just -. Now you must type func dynamic. This reminds me of how you have to type nonatomic on most Objective-C properties because the default is usually not what you want.

The latest version of Using Swift with Cocoa and Objective-C says:

While the @objc attribute exposes your Swift API to the Objective-C runtime, it does not guarantee dynamic dispatch of a property, method, subscript, or initializer. The Swift compiler may still devirtualize or inline member access to optimize the performance of your code, bypassing the Objective-C runtime. When you mark a member declaration with the dynamic modifier, access to that member is always dynamically dispatched. Because declarations marked with the dynamic modifier are dispatched using the Objective-C runtime, they’re implicitly marked with the @objc attribute.

Requiring dynamic dispatch is rarely necessary. However, you must use the dynamic modifier when you know that the implementation of an API is replaced at runtime. For example, you can use the method_exchangeImplementations function in the Objective-C runtime to swap out the implementation of a method while an app is running. If the Swift compiler inlined the implementation of the method or devirtualized access to it, the new implementation would not be used.

The good news is that Apple has found a way to speed up Objective-C classes in Swift. Presumably, this only applies to methods that are compiled into the same binary as their callers. My guess is that calls into the Cocoa frameworks cannot be optimized in this way.

The bad news is that, by default, your objects will no longer work with KVO or swizzling. If the designer of a class doesn’t anticipate a reason for a particular method or property to be dynamic, that lack of foresight gets baked in at compile time.

In my view, this is a mistake. It makes sense for dynamism to be the default because the costs are low. Objective-C message dispatch is very fast—famously running on 68030 hardware and of course the original iPhone. And, secondly, the Pareto principle applies: when message passing is a bottleneck, most of the gains can be obtained by making just a few hot spots more static (e.g. using IMP-caching).

The costs for not using message passing, on the other hand, can be high because they make the code more rigid/brittle. You cannot retroactively make compiled code more dynamic/flexible. And yet, since dynamic is not the default, the odds are that a lot more methods will be static than need to be. Most of the time, objc_msgSend is not why your code is slow, yet Swift acts like it needs to protect you from this.

The common case is that message sending is fast enough and that programmers are not smart enough to predict all the ways their code will be used in the future. So Apple optimize for that and make dynamic the default.

14 Comments

Whilst I'm concerned about the potential for bugs caused by losing KVO by default, I don't think it's reasonable to call message sending 'fast enough'. When it comes to responsiveness, it is true that message send is not the bottleneck for responsiveness, but when it comes to switching cores off to conserve power, any performance gain translates into a power reduction.

Percentage improvements in performance that lead to no perceptible responsiveness gain translate into devices that can work for longer on a charge, and have longer working lifetimes. Now that we are in the realm of 1BN iOS devices, this matters a lot.

@Robin I don’t understand your argument. Are you suggesting that there is a non-linearity, i.e. that a small overall increase in performance leads to a much larger power savings? Or are you saying that power saving is so important that we must sacrifice everything else for it?

I’m missing something. If you need dynamic messages it’s so easy to add why would you need to do it *after* compile time? Why not just rebuild and mark the appropriate parts as dynamic? I must be missing the problem that can’t be solved with a recompile.

@Clark The methods that need to be dynamic might not be in your code.

I've used IMP caching or Core Foundation for speed a few times in 10 years of Cocoa, but probably less than a dozen. Dynamism is a lot more useful in general; it's what I like about Python and dislike about C++, both of which I learned after C/Obj-C. I agree that dynamic messaging as default would be a better fit for someone transitioning from Cocoa. I'm not really a fan of swizzling and KVO, though, and I wonder if common dislike of debugging KVO problems is part of what's driving this apathy?

Michael,

Thing you are missing is that Universities are no longer even offering OOP to first year students.
Most of the hot shots are moving to Functional Programming and its promise of making
it easy to do multi-core programming. Functional Programming is promising
less complexity and less bugs.
OOP had its day now new generation is going to teach us new and wonderful things not
possible. You don't see anyone reviving Smalltalk that is also Obj-c without C.

Chris Lattner's philosophy is you can write your code in any typed language and
LLVM will optimize it to performance of C. He hasn't been able to the same for Obj-C.

Doesn't swizling defeat the purpose of security and safety. If Apple can rewrite
WebObjects and EOF in Java.

Man I miss smalltalk. Dr no had to go there :-/

@dr.no I’m not sure that most universities ever really taught true OOP. Functional programming is great for algorithmic code, but it’s not as proven for the type of outer framework code and application code that we’re talking about. We’ve certainly seen when happens when people use a static language like C++ for this.

Of course, Alan Kay himself is reviving Smalltalk as Squeak, though I’m not sure how much traction is has. Smalltalk also had a big influence on Ruby, which is quite popular these days. Unfortunately, I don’t think something like Rails could currently be written in Swift.

Given all the information available to the Swift compiler, I would assume that it should be able to perform better than C.

Swift is deliberately not trying to be secure, in the sense of running in a VM like Java or .NET. There are all sorts of things that can make code unsafe. For example, currently a Swift program will blow up if it encounters an unexpected nil value, whereas Objective-C would not. Swift would also be safer without access to raw pointers and C, but of course there are lots of benefits to including that integration. Swizzling can certainly be abused, but it’s also the key behind great technologies like EOF and KVO, and it makes it possible to work around bugs in compiled code.

My understanding is that WebObjects and EOF didn’t work as well in Java as in Objective-C and that Swift is not dynamic enough to write either.

a quote From Wikipedia article of Comparison of programming paradigms:
Carnegie-Mellon University Professor Robert Harper in March 2011 wrote: “This semester Dan Licata and I are co-teaching a new course on functional programming for first-year prospective CS majors… Object-oriented programming is eliminated entirely from the introductory curriculum, because it is both anti-modular and anti-parallel by its very nature, and hence unsuitable for a modern CS curriculum. A proposed new course on object-oriented design methodology will be offered at the sophomore level for those students who wish to study this topic.”[15]

New kids aren't going listen to your war stories, they will make their own mistakes
and not be able to pass on their knowledge to next generation. that is the curse
of computer/engineering.
Industry didn't learn anything from c++ as gui framework debacle. it moved on to Java, JS, HTML.
Obj-c only survived because of iphone otherwise we had this same angst
when Java came on to the scene.

ambition of Functional programming is that it is Block type programming
with its small code with read only data that can be executed in parallel.

VM has nothing to do with security. It is just dynamically linking and optimizing code on the run.
All the insecurity of Java bug should be proof of that.

Mozilla took JS convert to asm and pipe to LLVM to produce native code speed.
So Swift should be just as fast as c++.
In my mind Swift will be successful if it can kill c++.

WebObjects after conversion to Java had all the features of Obj-c but you could
still drop down to obj-c if you wanted to. EO design had little more complexity
otherwise it was the same.

Brent Simmons:

This die-hard speed freak has never been concerned with the speed of objc_msgSend. I’ve noticed it in Shark and in Instruments, but the real performance issues were elsewhere in my code.

[…]

Still, though, there is code in Vesper that can’t be ported to pure Swift. (Model code.) The point that you can use something like Core Data but couldn’t write it in Swift remains important.

Ilja A. Iwas:

None of the apps we are currently shipping could be done in Swift, because they build Objective-C representations of eBay’s XML at runtime.

I believe the issue for Swift's designers is not performance, but rather a totally different design perspective.

Most of what is done dynamically can be done through compile-time metaprogramming (much of Core Data for example). Other techniques relying on dynamism are more brittle than some alternatives. For example, I have seen a couple talks discussing how Facebook is moving away from mutable models (and implicitly KVO) to a functional approach that replaces the entire model using persistent data structure techniques.

Swift is making a strong move in the direction of programming techniques that are easier to reason about, easier to test, and less birittle. If you don't want to learn new ways of doing things Swift may not be for you.

To anyone reading this comment, please add this suggestion to the appropriate radar so Chris Lattner and team may hear about it.

There is a very valuable use case for the dynamic modifier keyword, which does not seem to be yet addressed in Swift. The dynamic modifier keyword needs to be available for use during the method call, in addition to its use in the declaration of a prototype or implementation.

It is a very common polymorphism technique to assign a descendent reference to a variable of its ancestor class. But a static dispatch determined at compile time would not enable the descendent's extra methods to be called. This is a classic CS problem which drove the invention of generic types, although generics do not go far enough to address every situation.

A very easy to handle solution, is to allow the programmer to make a call to a method which only exists in the descendent, by adding the dynamic keyword to the method call syntax, thereby causing the compiler to substitute dynamic dispatch for that particular invocation. The programmer is asserting to the compiler that the method will exist at run time. (It's then a programmer mistake and a run time error if the method does not exist when the dynamic keyword was used.)

There are many benefits to this approach:

Method calls may be usually static for speed, but dynamic only when required, such as when iterating over a polymorphic collection.

The author of a class does not have to plan for how the class would be used by inheritance. Abstract methods (i.e. stubs) may not need to be created at all, and ancestor classes may not need to declare their methods as ALWAYS dynamically accessed. They can be statically dispatched by default, and optionally dynamically dispatched by certain consumers of the class.

This is technique has been used by PowerBuilder programmers during the last 20 years or so, to optimize performance while avoiding the need for generic types in event-driven GUI code. It is very easy for the programmer making the method call to know when the dynamic dispatch will be required.

Not every dynamic dispatch needs to go through a message queue, and the dynamic call keyword in PowerBuilder does not place the call message into a queue. The call is still immediate. But there is also a separate (and occasionally very useful) POST keyword in PowerBuilder, which allows any method call to be placed in a message queue for deferred invocation. This is a convenience for dealing with common GUI scenarios when an ancestor needs to allow the descended classes to complete their work for the current method before invoking another method. Interestingly, it is possible to specify the POST keyword without the dynamic keyword, and PowrBuilder will queue the method call, but still invoke it later using the static call semantics.

I hope this idea is helpful and makes it into the Swift language.

[…] regular cached dispatch is fast enough that it isn’t worth using vtables, why is Apple discouraging message passing on performance […]

Jason Brennan:

Swift is a better way to create the exact same kind of software we’ve been making with Objective C. It may crash a little less, but it’s still going to work exactly the same way. And in fact, because Swift is far more static than Objective C, we might even be a little bit more limited in terms of what we can do.

Stay up-to-date by subscribing to the Comments RSS Feed for this post.

Leave a Comment