Optimizing calls to methods on extensions that are inside a Swift Package

While doing some refactoring and profiling recently, I noticed that a relatively benign function I call in a tight loop was now showing up in an Instruments trace. The function was recently moved from the main application into a Swift Package.

When a function is moved from the main application source base into a Swift Package, what impact does that have on the compiler's ability to optimize calls to that function? I'm a bit confused as the differences between whole-module-optimization and what I see sometimes referred to as cross-module-optimization.

In Swift 5.5, are there things I can do improve the optimizer's outcome? Is this a case where @inlinable would be appropriate?

The Swift Package is private and only used internally. While this post only shows one example, the package includes numerous extensions with similar helper functions and properties.

Xcode 13 Beta 5, macOS 12 Beta 21A5506j, Release Build profiled in Instruments.

In MyPackage.swift:

extension Collection {
  public var hasElements: Bool {
    !isEmpty
  }
}

In MyApplication.swift:

// Using this implementation, the call to hasElements 
// will show up in the Instruments' trace.
func addItem(...) {
  if managedItems.hasElements {
    
  }
}

// Using this implementation, no noticeable call is 
// showing up in Instruments.
func addItem(...) {
  if managedItems.count > 0 {
    
  }
}

The Instruments' Trace:

And yes, I recognize that in this trace we're "only" talking about 92ms of profiled time. I'm not that interested in whether this should be of concern or not, but more interested in the semantics as to why this shows up at all and if it can be avoided... :grinning:

Yes.

You should also try @inline(__always). Make you you understand the difference between @inline(__always) and @inlineable. See

I wouldn’t recommend trying underscored features unless you know exactly what you want from them and are able to deal with the consequences when it goes sideways. Keep in mind that they’re underscored features precisely because they’re not robust enough to be public.

In this case, heed the warning that @inline(__always) overrides the compiler’s inlining heuristics and may be a microbenchmark win or may cause catastrophic issues with code size and/or performance. It also has nothing to do with addressing the question asked, for which @inlinable is what you’re reaching for.

2 Likes

@inline(__always) is related to this thread because adding it may improve performance, which is what the OP was asking about.

Great, thank you. I assumed as much but wasn't sure if @inline or something else was more appropriate. (Or if things had changed at all in Swift 5.5.)