In Swift 4, the code size was about
2.3x the size of the Objective-C version. In Swift 5.3, the code size is
under 1.5x the size of the Objective-C version.
If you mean the remaining .5x overhead above Objective-C, well, that’s a mature language from the 80s calling itself we’re comparing it to. So I’ll focus on improvements since Swift 4 which I think you’re more likely to be asking about.
Lack of prior ABI stability is my guess.
In order to bootstrap Swift by using the Objective-C dev community as a base audience, Swift had to have strong Objective-C interop to call both existing code and system frameworks. That was in place when they publicized the language in 2014, but it worked by being compiled into the app, because the language and ABI were still in flux. Swift 4 introduced source code stability; Swift 5 introduced ABI stability. So until very recently, Swift programs had to include their own Swift bindings to the system frameworks they used, at the ABI version they consumed. Bindings couldn’t be shared with other programs built at other versions. Now that the ABI is stable, those can be provided by the OS just like the system frameworks themselves. That commonly reduced iOS app download sizes on the order of 10MB.
I’m not sure whether those bindings would have been counted in this measurement as they’re treated as linked frameworks instead of the main binary. But I imagine there are other optimizations that had to be delayed until ABI stability, either because they rely on it or because ABI stability was the major goal prior to that time and some other things had to wait.
Another thing that had to wait for ABI stability is system frameworks in native Swift, which might reduce the burden of object translation on the way to Objective-C calls. That would apply especially if the new app uses SwiftUI instead of UIKit, but from rereading, I don’t think that’s the case.
Binary size improvements will vary by patterns of use. The biggest improvement is in projects that declare a large number of types, through reduction in the size of “value functions” – the invisible functions that the compiler generates to create, copy, and destroy value types.
The value types described here are structs and enums, as opposed to classes which are reference types. They can be generic, which is probably how you get a large number of concrete value types that need their own implicit value semantic implementations, like being copied when passed to a function. Value types in Swift are first-class in the sense of having methods, generics, and protocol conformance (interfaces, traits). There is a good amount of background work involved in providing and optimizing generics, interface polymorphism, and the choice of reference or value semantics, while making all those kinds of method calls look to the programmer like the same thing. If this sounds interesting, here’s a talk about it.
My team develops web services on macOS and deploys to Linux/Docker/Kubernetes. Working great so far. I wrote just a little bit about it here: https://lobste.rs/s/havoue/introducing_swift_cluster_membership#c_xbmwqv I’ve not heard of any stability issues with it running on Linux, though you may run into some things being unavailable of course.
I felt the core of Swift is stable on Linux. But how about these components such as Swift-NIO (noticed it is a dependency for you)? Is it something you don’t have to worry about anymore like net in Go? Do they comparable in performance? How about things like Dispatch on Linux?
Happily, we haven’t encountered enough problems here for me to have had to dig into it enough to say anything intelligent. If Swift-NIO/Dispatch have some differences, Vapor papers over them enough that we’re not aware of them. We haven’t used Swift-NIO directly (aside from those types from it that Vapor exposes) but we use Dispatch some and haven’t noticed any differences from how we do things on Apple platforms. And we haven’t had any issues with runtime stability either. That said, most of what we’re doing is fairly basic, so others certainly may have different experiences.
What’s the reason of this overhead in Swift?
If you mean the remaining .5x overhead above Objective-C, well, that’s a mature language from the 80s calling itself we’re comparing it to. So I’ll focus on improvements since Swift 4 which I think you’re more likely to be asking about.
Lack of prior ABI stability is my guess.
In order to bootstrap Swift by using the Objective-C dev community as a base audience, Swift had to have strong Objective-C interop to call both existing code and system frameworks. That was in place when they publicized the language in 2014, but it worked by being compiled into the app, because the language and ABI were still in flux. Swift 4 introduced source code stability; Swift 5 introduced ABI stability. So until very recently, Swift programs had to include their own Swift bindings to the system frameworks they used, at the ABI version they consumed. Bindings couldn’t be shared with other programs built at other versions. Now that the ABI is stable, those can be provided by the OS just like the system frameworks themselves. That commonly reduced iOS app download sizes on the order of 10MB.
I’m not sure whether those bindings would have been counted in this measurement as they’re treated as linked frameworks instead of the main binary. But I imagine there are other optimizations that had to be delayed until ABI stability, either because they rely on it or because ABI stability was the major goal prior to that time and some other things had to wait.
Another thing that had to wait for ABI stability is system frameworks in native Swift, which might reduce the burden of object translation on the way to Objective-C calls. That would apply especially if the new app uses SwiftUI instead of UIKit, but from rereading, I don’t think that’s the case.
The value types described here are structs and enums, as opposed to classes which are reference types. They can be generic, which is probably how you get a large number of concrete value types that need their own implicit value semantic implementations, like being copied when passed to a function. Value types in Swift are first-class in the sense of having methods, generics, and protocol conformance (interfaces, traits). There is a good amount of background work involved in providing and optimizing generics, interface polymorphism, and the choice of reference or value semantics, while making all those kinds of method calls look to the programmer like the same thing. If this sounds interesting, here’s a talk about it.
Thanks a lot for the thorough explanation!
I was actually wondering about the 50% above Objective-C, because they both use LLVM. It makes more sense now.
Has anyone used Swift on non-Apple platforms? I know there’s a Linux implementation, but is it suitable for real work/stable?
My team develops web services on macOS and deploys to Linux/Docker/Kubernetes. Working great so far. I wrote just a little bit about it here: https://lobste.rs/s/havoue/introducing_swift_cluster_membership#c_xbmwqv I’ve not heard of any stability issues with it running on Linux, though you may run into some things being unavailable of course.
I felt the core of Swift is stable on Linux. But how about these components such as Swift-NIO (noticed it is a dependency for you)? Is it something you don’t have to worry about anymore like net in Go? Do they comparable in performance? How about things like Dispatch on Linux?
Happily, we haven’t encountered enough problems here for me to have had to dig into it enough to say anything intelligent. If Swift-NIO/Dispatch have some differences, Vapor papers over them enough that we’re not aware of them. We haven’t used Swift-NIO directly (aside from those types from it that Vapor exposes) but we use Dispatch some and haven’t noticed any differences from how we do things on Apple platforms. And we haven’t had any issues with runtime stability either. That said, most of what we’re doing is fairly basic, so others certainly may have different experiences.
How is the WebAssembly support ?