The Objective-C Runtime and Swift Dynamism
academy.realm.ioThis is particularly relevant right now. The swift-evolution mailing list is currently discussing adding a "dynamic member lookup" protocol to Swift to enhance interop with dynamic languages like Python, Ruby, and Perl. You can see the proposal here[0].
[0] https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae...
I still like Objective-C even if it isn't my go-to language anymore. It's quite elegant compared to it's peers from the 80's, it's a very small language yet you can achieve everything with it, even if it means dipping into C(++) for the parts that need to be really performant. But on the other hand seeing a different programming language when there's some optimization going on and having another programming language for business logic is a great thing too.
How many times don't we see bugs, errors or security issues stemming from the fact that business logic gets implemented in C?
What is cross-platform development like?
Also, what about Objective-C prevents business logic bugs?
> What is cross-platform development like?
Sorry, never tried that.
> Also, what about Objective-C prevents business logic bugs?
Generically speaking C makes it easy to interact with the computer memory but harder to abstract things while OO languages have it reverse.
C has GOTO, Objective-C not.
Objective-C has objects that easily can encapsulate different aspects of your logic. Objective-C is very verbose and readable while most C API's aren't.
Obviously Swift is even better in that regard as it has better types and checks but it doesn't allow you to seamlessly use C.
I'm getting the loading error, "An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details."
Not sure if there is a mirror, a write-up, or another example anyone might be able to pass along.
edit: this google cache version is working[0].
[0] https://webcache.googleusercontent.com/search?q=cache:gXiqzW...
You can watch the talk with google cache. It’s from 2016.
Basically, Swift dynamism is coming, hopefully safer than ObjC
> Before we start, Objective-C is a runtime-oriented language, which means that all of the links between your methods and variables and classes are deferred to the last moment possible to when your app is actually running, and this gives you great flexibility because you can change those links. The alternative, where Swift is most of the time, is compiletime-oriented languages. So in Swift everything is a bit more harder bound, and you get more safety, but it’s less flexible.
> This is what this debate was all about.
Not just safety, but also performance. Swift offers significant performance gains when you don’t need the dynamism. Swift lets you opt into dynamic features (as he later explains), and you can seemlessly interact with Objective C on Mac platforms. But you don’t have to pay the penalty for dynamism in the vast majority of your code that doesn’t use these features.
Edited: I also pointed out some additional mistakes in the post, but removed that because this comment got too long.
Tooling and maturity are reasons why not currently, maybe give it a couple of years ;)
Debuggers can not work properly, xcode indexing can beachball, compile times are quite a bit longer, there is API churn from version to version, a lack of ABI stability forcing you to bundle swift with your app increasing binary size and other bugs.
> Not just safety, but also performance. Swift offers significant performance gains
This is a common misconception. It is not true. Swift is slower than Objective-C, typically significantly so.
When arguing against a “common misconception,” one typically provides a source.
You seem to be saying that Apple’s claims are not only wrong, but ObjC is somehow faster despite having to do more work. Swift has no runtime method table lookups, no extra objc_msgSend calls, better ability to analyze and inline code, no retain/release dance for code with value semantics, etc. Every benchmark I’ve seen shows Swift faster for these reasons.
Do you have experience writing Swift or Obj-C programs? My experience is that Obj-C has predictable performance which is often "good enough", whereas Swift is either too slow (without optimizations) or takes a very long time to compile (with optimizations enabled).
As a concrete example, I took a Swift program I had lying around (8 files, 2854 lines total), compiled it without optimizations, and stress-tested it a bit. Here's what the "bottom-up" profile looks like: https://i.imgur.com/ohlKQgx.png -- tons of time wasted in Swift runtime overhead. Compiling with optimizations removes most of the overhead, but a clean build of this (relatively small) program took almost 5 minutes with optimizations enabled.
Did you compare it to a roughly equivalent Objective-C program? The top four entries in your profile data show retain, release and objc_msgSend. You will have those in Objective-C too. Maybe to a different degree? That's also why I am asking whether you have similar Objective-C code to test. Or maybe it's just that unoptimized Swift code is slower and optimized code is faster?
Compile times are a related but different matter. There are -warn-long-function-bodies and recently -warn-long-expression-type-checking which are really helpful and can give you an idea where most of the compile time is spent. In my experience, the type checker can spend a lot of time in mildly complex expressions involving overload resolution, which can be really annoying but there are often ways around it. With those culprits being eliminated, I have never encountered 5 minute build times for projects of this size or bigger, and I like to imagine that I write fairly generic code.
Thanks, I didn't know about those flags! I suspect there was a pathological expression somewhere, since most of that time was spent in a single file.
It should be easy to construct Swift code that is much than its ObjC equivalent by failing to account for copy-on-write:
https://medium.com/folded-plane/swift-array-appending-and-av...
So Swift is faster than ObjC by a constant factor, until you run into this subtle issue (which didn't really exist in ObjC), and then it's suddenly slower by a full O(n). Whether that's a good trade-off depends on how good of a language lawyer you are. It's C vs C++ all over again...
There are many cases like that. How was that with protocol generics and collections? When can it remove ARC calls?
> It's C vs C++ all over again...
From a performance POV, it seems to be a lot worse than C++. Much less baseline performance, less predictability, and almost no way to get to peak performance.
> are not only wrong
Yes, Apple's claims are wrong, sometimes comically so, particularly when it comes to performance. Remember when they claimed that GC was many times faster than retain/release? Also wrong. Or their claim that you should use property lists only for smaller data sets, and keyed archiving for larger data sets? Completely wrong, as keyed archiving uses property lists in its implementation, and always generates larger plists than if directly expressed. So a keyed archive is always worse, performance-wise, than an equivalent plist. And so on and so forth.
Anyway, I have a whole chapter in my book on this, and the numbers tell the story. I obviously can't reproduce the whole thing here.
> but ObjC is somehow faster despite having to do more work.
Another misconception. Swift does a lot more work. It then tries to remove that work through optimization efforts, which may or may not succeed.
A tiny, somewhat extreme example: Swift allocates local variables on the heap. Not as a frame, but individually. At least initially. Now of course this would make code many orders of magnitude slower, so the optimizer (this is a mandatory pass, run even at -O0) has to remove this if it can. However, this is just an optimization, so as far as I can tell there is no diagnostics if it fails.
See https://www.youtube.com/watch?v=Ntj8ab-5cvE
Slides: http://llvm.org/devmtg/2015-10/slides/GroffLattner-SILHighLe...
Then there is mandatory/invisible ARC, which can and will get you in an inner loop, without visibility, and with not much recourse. Even Chris has admitted that this is a problem they need to fix. And of course generics are implemented via witness tables, so indirect dispatch at roughly similar costs to objc_msgSend(). The compiler may be able to eliminate this. Or not.
And so on and so forth. I just saw something about blocks always causing heap allocations (and this is corroborated by an attempt someone made to port some HTTP parsing code from C to Swift. Even with max. optimizations and inline craziness, it was ~3x slower).
Or JSON "parsing". The Swift solutions that tend to sit on top of NSJSONSerialization have tended to be an order of magnitude slower than NSJSONSerialization by itself. Which is odd when you consider that NSJSONSerialization uses all the slowest aspects of Objective-C/Foundation: keyed access, heap allocated objects for things that would otherwise be primitives, dictionaries instead of objects (typically 10x slower) etc. Yet Swift on top is 10x slower. The BigNerdRanch's "Freddy" JSON parser tries to rectify those problems by being 100% Swift, without NSJSONSerialization underneath. The result is that it's "only" 4-5x slower than NSJSONSerialization. And again, NSJSONSerialization isn't particularly efficient.
I haven't tested the Swift 4 serialization stuff yet, but both from reports I've heard and cursory looks at the implementation, it doesn't look like a speed demon.
> Every benchmark I’ve seen shows Swift faster for these reasons.
What benchmarks are you looking at?? While it wouldn't be true that I've never seen a Swift advantage, it's pretty close to never.
Now there are a lot of unsubstantiated claims that Swift is fast, because "reasons", but benchmarks?
I can't say anything about JSON serialization performance as I don't have experience with it. Your other points, though, seem a little handwavy to me. Or maybe I am reading them wrong.
> A tiny, somewhat extreme example: Swift allocates local variables on the heap. (...)
Have you ever encountered a local variable that spuriously didn't get stack promoted? I haven't. As I said elsewhere, I regularly read the generated code for my hot loops. Also, when profiling with Instruments, I have never been surprised by a heap allocated local variable that didn't escape. I also don't see why stack promotion theoretically would be a less precise analysis then doing it the other way around. I imagine that if the optimizer misses to promote a local variable, it would be a bug in the same way it would be a bug if an escaping local variable spuriously didn't get boxed (for compilers working the other way). Just that it won't fail at runtime, which might increase the potential for undiscovered bugs. But again, have you ever been bitten by this?
> And of course generics are implemented via witness tables, so indirect dispatch at roughly similar costs to objc_msgSend()
Generic types are opportunistically specialized and in my experience, the optimizer has gotten a bit better in that regard. I find that a nice compromise between C++ and, say, Java. You can also influence the optimizer's decision with various not-yet-stable annotations (@specialized, for example). Sure, if you want to write reliably fast generic code in Swift, you need to know a few things. None of the above is possible in Objective-C, though, because of its type system.
> I just saw something about blocks always causing heap allocations (and this is corroborated by an attempt someone made to port some HTTP parsing code from C to Swift. Even with max. optimizations and inline craziness, it was ~3x slower).
If by blocks, you mean closures, then yes, they are heap allocated if they escape. For non-escaping closures, there is always a way to force an inline unless you pass them to compiled third-party code. Cross-module optimization is an area that is still being worked on. Without knowing anything about the code in the benchmark, from your description, it sounds to me that there is unused potential for optimizations, either by making the code more idiomatic and/or by using one or two annotations. Which brings me to my last point.
> What benchmarks are you looking at?? While it wouldn't be true that I've never seen a Swift advantage, it's pretty close to never.
Do you have links? Not that I looked too thoroughly, but I have never encountered a benchmark comparing Swift with Objective-C (or other languages?) that both (1) showed significant worse performance for Swift across the board and (2) that I trust. Most recent code I have seen that does not perform well could fairly easily be improved or would have to be rewritten in more idiomatic Swift. I specifically say most, since there certainly is still room for improvement, but in my experience it is nowhere as bad as your comment suggests.
> Generic types are opportunistically specialized and in my experience, the optimizer has gotten a bit better in that regard
That's always the answer: "the compiler has gotten better and will get better still". Your claim was that Objective-C has all this "extra work" and indirection, but Swift actually has more places where this applies, and pretends it does not. With Objective-C, what you see is what you get, the performance model is transparent and hackable. With Swift, the performance model is almost completely opaque and not really hackable.
>None of the above is possible in Objective-C, though, because of its type system.
What does the "type system" have to do with any of this? It is trivial to create, for example, extremely fast collections of primitive types with value semantics and without all this machinery. A little extra effort, but better and predictable performance. If you want it more generically, even NeXTSTep 2.x had NXStorage, which allowed you to create contiguous collections of arbitrary structs.
Oh...people seem to forget the Objective-C has structs. And unlike Swift structs they are predictable. Oh, and if you really want to get fancy you can implement poor-man's generics by creating a header with a "type variable" and including that in your .m file with the "type variable" #defined. Not sure I recommend it, but it is possible.
The fact the Foundation removed these helpful kinds of classes like NXStorage and wanted to pretend Objective-C is a pure OOPL is a faulty decision by the library creators, not a limitation of Objective-C. And that Foundation was gutted by CoreFoundation, making everything even slower still was also a purely political project.
In general, you seem to be using "Objective-C" in this pure OOPL sense of "Objective-C without the C" (which is kind of weird because that is what Swift is supposed to be, according to the propaganda). Objective-C is a hybrid language consisting of C and a messaging layer on top. You write your components in C and connect them up using dynamic messaging. And even that layer is fairly trivial to optimize with IMP-caching, object-caching and retain/release elision.
Chapter 9 goes into a lot of details on Swifft: https://www.amazon.com/gp/product/0321842847/
A few Swift issues surprised me, to be honest. For example native Swift dictionaries with primitive types (should be a slam dunk with value types and generics) are significantly slower than NSDictionary from Objective-C, which isn't exactly a high performance dictionary implementation. About 1.8x with optimizations, 3.5x without.
This is another point. The gap between Swift and Objective-C widens a lot with unoptimized code. Sometimes comically so, 10x isn't unusual and I've seen 100x and 1000x. This of course means that optimized Swift code is a dance on the volcano. Since optimizations aren't guaranteed and there are no diagnostics, your code can turn into a lead balloon at any time.
And of course debug builds in Xcode are compiled with optimization off. That means for some code either (a) the unoptimized build will be unusable or (b) all those optimizations actually don't matter. See "The Death of Optimizing Compilers" by D.J. Bernstein.
Anyway, you asked for some links (without providing any yourself):
https://github.com/helje5/http-c-vs-swift
https://github.com/bignerdranch/Freddy/wiki/JSONParser
"Several seconds to parse 1.5MB JSON files"
https://github.com/owensd/swift-perf
But really, all you need to do is run some real-world code.
You also mention looking at the assembly output of the Swift compiler to tune your program. This alone should be an indication that either (a) you work on the Swift compiler team or (b) you are having to expend a lot more effort on getting your Swift code to perform than you should. Or both.
I can't speak for other people's code, but I regularly profile and read the machine code generated by the Swift compiler, at least for my hot loops. If you know what you are doing (use the right annotations and optimizer flags), even fairly generic code often compiles down to something that comes very close to what an optimizing C compiler would generate. Sometimes it generates even faster code, because the Swift calling convention can make better use of available registers. Sure, there are situations where it generates less optimal code, but generally generic idiomatic Swift is on a different level than a (non-profiling) compiler for idiomatic Objective-C can ever come close to. That's my experience.
> very close to what an optimizing C compiler would generate.
Hmm...with Objective-C, I get exactly what an optimizing C compiler "would" generate, because that's an optimizing C compiler generating it.
>"Before we start, Objective-C is a runtime-oriented language, which means that all of the links between your methods and variables and classes are deferred to the last moment possible to when your app is actually running, ..."
I had not heard the term "runtime-oriented language" before. Is this really just another term for "supports reflection"? If not what would be other examples of "runtime-oriented" languages?
The usual name for this is "late binding". It became popular because of Smalltalk, and it's a core principle of the "pure OOP" languages (like Self, Obj-C and Ruby) that Smalltalk inspired.
I think it means "dynamic," in the sense that when you call a method, the object's class is looked up and the class's method list is looked up and the methods' implementations are looked up and the class is even given a chance to handle the lookup itself. It doesn't just support reflection, it reflects pervasively. This basically corresponds to what we usually mean when we say a "dynamic language."
Is that lookup done all the time (for each invocation of the same method):? Or is it done at a single point in time, like a JIT, but after that the method is “fixed”?
With the possibility to intercept/replace code at runtime (at any time, not just at a resolve/jit stage) it must be very hard for the runtime to optimize calls (i.e make direct calls instead of indirect via method pointer lookup)?
I’m not familiar with how this works in any runtime (V8, Hotspot, ...) so I’m curious which runtimes actually pay one extra method pointer lookup forever and which don’t. I’m guessing the answer for nearly all of them is “it depends”.
Many languages use "inline method caching" - that is, a "call_dynamic x_named_method" bytecode is replaced with a "guard_and_call_static 0xfffffff" once the callsite is evaluated. In some languages invalidating this cache by calling a method with polymorphic arguments or altering the receiving class can be extremely expensive, so there are usually second-order optimizations applied.
Objective-C: Takes one method pointer indirection / jmp trampoline forever, but caches "selectors" on the class so that they become a simple lookup rather than a full evaluation. See sibling comments for better write-ups than I could find.
Most JS runtimes: Trace JITs mean many method invocations are compiled into traces and become either inline instructions or native function calls. Also uses "hidden classes" to implement inline method caching, where the callsite is replaced with the specific address of the call once the dispatch is resolved. https://github.com/sq/JSIL/wiki/Optimizing-dynamic-JavaScrip... , https://blog.ghaiklor.com/optimizations-tricks-in-v8-d284b6c...
Ruby: Also uses inline method caching - when a method is resolved that callsite is replaced with a jump straight to the resolved method in the bytecode. Invalidation used to occur any time a class was modified in any way but has been made more specific over time: https://github.com/charliesome/charlie.bz/blob/master/posts/...
Conceptually for every call. In practice, a language can do less, as long as it always calls the correct method.
How exactly it finds that method is an implementation detail, but fast implementations will cache the results of method lookups. For example, https://developer.apple.com/library/content/documentation/Co... states:
”To speed the messaging process, the runtime system caches the selectors and addresses of methods as they are used. There’s a separate cache for each class, and it can contain selectors for inherited methods as well as for methods defined in the class. Before searching the dispatch tables, the messaging routine first checks the cache of the receiving object’s class (on the theory that a method that was used once may likely be used again). If the method selector is in the cache, messaging is only slightly slower than a function call. Once a program has been running long enough to “warm up” its caches, almost all the messages it sends find a cached method. Caches grow dynamically to accommodate new messages as the program runs.”
The lookup happens on each invocation. It’s alway indirect, and the implementation of a method can be changed at runtime. Method calls aren’t optimized to be direct invocations.
There's some good info on that here:
"Dissecting objc_msgSend on ARM64" https://www.mikeash.com/pyblog/friday-qa-2017-06-30-dissecti...
"Let's Build objc_msgSend" https://www.mikeash.com/pyblog/friday-qa-2012-11-16-lets-bui...
There's also the "Illustrated history of objc_msgSend", although it appears a bit dated, it has some nice commentary on the evolution of this very performance-critical part of the objective-c runtime: http://sealiesoftware.com/msg/index.html
So more or less dynamic dispatch then?
Yes, a "dynamic language" is generally one where all method calls are dynamically dispatched. They're usually implemented using "message-passing" terminology (ex. "send" in Ruby, "objc_msgSend" in Objective-C) as a throwback to Smalltalk, which inherited the terminology from Actor systems even though the semantics differ substantially.
Really just means a dynamic language just like smalltalk, LISP, python, Ruby etc. Like Objective-C all these allow you to add classes and method at runtime.
It's interesting that Swift is a static language, despite having the lightweight modern syntax similar to Python or Ruby. Though I suppose Go is the same way.
Haskell is one of the most strictly static languages around, and its syntax is super light weight.
I think the author means "late binding" - the method lookup is done at invocation time rather than compile time. (There is caching, etc, to make it fast.)
Smalltalk
It's important to note that this talk is from 2016. Since then, Swift 4 has been released, that added KVO and key paths to the language.