Swift's unpredictable efficiency

Even in the short term, @inlinable doesn't really help with this performance problem since it can only be applied to public declarations. We either need a compilation mode that allows cross module optimization or a new attribute that isn't tied to compiling in resilient mode.

1 Like

Isn't that what @usableFromInline is for?

See comments to @Joe_Groff also; but to address your (@jawbroken) comments specifically.

The three specific attributes you mention:

  1. @escaping: Can't see why the compiler can't do this for me; but see comments to @Joe_Groff re linking system libraries.
  2. @objc: Probably needed when you write a Swift class to be used by Obj-C. Not needed when a Obj-C class to be used by Swift. OK with this as part of progressive disclosure since you are doing something quite advanced if you are writing Swift to be used by Obj-C and not extending an Obj-C protocol or class.
  3. @IB*: Other IDEs (non-Xcode) that allow users to graphically compose windows don't require these annotations, so not strictly necessary. However I am less bothered by these because they are more Xcode than Swift. Ideally though get rid of them because the obscure code and I have found them to be a source of bugs.

You also mention progressive disclosure. I don't find it very progressive if as soon as you scratch the surface and do something like pass a closure you have to start annotating (@escaping) or as soon as you want to write a library that is as performant as an Apple one you have to start annotating (@inlineable). To me these are ordinary things for a programmer to do, not advanced concepts that justify extra language features. Though obviously this is a subjective judgement by me, others might find closures advanced for example.

2 Likes

I would definitely support making this Apple only.

Definitely think there is potential to do better than Android. I think their:

  1. Higher memory usage is due to garbage collection and keeping class files and compiled code in memory.
  2. Higher energy usage is due to doing a lot of compilation on device. I was only suggesting re-linking after an OS update.

Not "Apple-only" exactly, but "only for people shipping independently-updatable binary libraries". That's only Apple in the short term but could eventually include other middleware vendors, Linux distributions, and the like.

4 Likes

In case there's some confusion, @usableFromInline functions cannot be inlined across modules. It's purely a resilience attribute for exposing symbols across a binary interface.

I'll restate: today there's no way I know of to split an application into modules, use public as intended to express the module API, and allow optimization across the application's modules. It could be a serious problem if your modules have generic interfaces. You could partially workaround this by declaring all your functions and properties public and @inlinable, explicitly defining all implicit methods, but even then there's a lot of resilience cruft. It probably makes more sense for your build system to copy all the source into a single module for Release builds.

2 Likes

Right. Ideally this would be totally behind-the-scenes for most programmers, who would just need to tell their build system what modules they want built into any particular image, and then the build system would invoke Swift in a way that allows cross-module and even cross-image (if version-locked) optimization.

1 Like

Slava's blog (and the review thread for SE-0193) led me to believe otherwise.

Yeah, sorry, "Cross-Module Inlining" is an unfortunate title for a set of features that are designed to enable a resilient standard library. They weren't design as a way for regular developers (who don't care about resilience) to split their app into modules. For now, you're better off not splitting up the app if you discover that runtime performance is a problem. The standard library's performance issues are self-inflicted by building with resilience enabled. So the features described in those documents are super important, but not precisely relevant to this thread.

This is a huge issue, as it means that writing efficient third-party generic libraries that include data structures and operations on them is not feasible.

No, I think he might just be mistaken. If you read Slava's blog post I linked to above, it seems very explicit about @inlinable making the SIL available across modules. It also explicitly mentions that because of these annotations, they no longer have to compile the standard lib in resilient mode.

Well, there are still some issues with @inlinable and @usableFromInline, I think: there appears to be no way to make an @inlinable function that creates an internal structure from a default constructor for its own use.

You can't make the structure and the init @usableFromInline? I realize that means that you probably need to manually implement the constructor instead of using the synthesized one, but that's a problem that needs solving anyway for other reasons.

You can probably do that, but it's such a simple structure that it doesn't need a custom constructor.

The larger issue is that now I'm having to annotate pretty much every single function - all the public ones get @inlinable and the private ones get @usableFromInline, in which case I have to ask why this isn't the default.

<rant>
Especially since I keep typing @useableFromInline.
</rant>

MWE:

@usableFromInline struct PrivateBug {
  @usableFromInline let y: Int
}

public struct Bug {
    @usableFromInline let x: Array<Int>
}

extension Bug {
  @inlinable func getX() -> Int {
    let p = PrivateBug(y:x.count)
    return p.y
  }
}
/Users/seth/dev/swift/Bug/Sources/Bug/Bug.swift:11:13: error: initializer 'init(y:)' is internal and cannot be referenced from an '@inlinable' function
    let p = PrivateBug(y:x.count)
            ^
/Users/seth/dev/swift/Bug/Sources/Bug/Bug.swift:1:26: note: initializer 'init(y:)' is not '@usableFromInline' or public
@usableFromInline struct PrivateBug {

This is ugly. And silly. I'm really hoping that I'm not doing this correctly.

Does the MWE have any performance issues if you remove the annotations? Because I was under the impression that they were only needed in order to enable cross module specialization of generics. Which aren't really involved here, as the only generic type is Array<Int> which already has full support for cross module specialization.

The annotations exist largely so that they can build the standard library without having to use (and maintain) a special compiler mode to do so. The fact that they're also useful as a stopgap measure for 3rd party generic libraries was merely a nice bonus.

I don't know: it's a working example of the error and the crazy annotations, not of the performance issues.

Yes, but this whole thing started because I'm trying to implement a graph analytics library using public struct Graph<T: FixedWidthInteger>: SimpleGraph where T.Stride: SignedInteger and performance is horrible compared to when I incorporate the code directly into an application, which is specifically what I don't want to do (see Performance issues when using a library vs including in code). I confess that I have not had the patience to create an MWE for the performance issue.

Again, the annotations are just a stopgap measure because (for many reasons) they can't implement a hands off cross module specialization solution until after the ABI is stable, and they have a bunch of other things that need to be done in between.

Again, the annotations are just a stopgap measure because (for many reasons) they can't implement a hands off cross module specialization solution until after the ABI is stable

I appreciate that, but there's another problem: if you're creating a library that uses generics this way, your options are as follows:

  1. use Swift 4.2 and liberally sprinkle the annotations around, get rid of any default constructors, and be prepared to rewrite your code once this has been resolved;
  2. use Swift 4.2 and no annotations, and get a 50x performance penalty ([SR-8159] Performance regression from 4.1.2 to 4.2 · Issue #50691 · apple/swift · GitHub); or
  3. use Swift 4.1 or earlier and suffer "only" a 25x performance penalty.

There is no appealing option. This is not a good interim solution at all.