WWDC Quick Look 💓 By SwiftGGTeam
Improve app size and runtime performance

Improve app size and runtime performance

Watch original video

Highlight

Recompile your app with Xcode 14 and run it on iOS 16. Your app will be up to 4% smaller in size and launch faster—all improvements without modifying a single line of code.

Core Content

Protocol check slows down startup

Protocols are often used in Swift development. A function receivesCustomLoggableParameters should be used when runningasorisChecks whether the incoming object conforms to the protocol.

Checks that the compiler can optimize at compile time have been made. However, in scenarios such as generics, a large number of checks can only be completed at startup. Apple found in real apps that protocol checks sometimes took up half the startup time.

Four transparent optimizations for Xcode 14 and iOS 16

Apple has made improvements at both the compiler and runtime levels:

  1. Swift protocol check precalculation: Calculate the protocol check metadata in advance and put it into the dyld startup closure. Existing apps benefit directly on iOS 16.
  2. Objective-C message send size reduction: eachobjc_msgSendCall size is reduced from 12 bytes to 8 bytes (ARM64), reducing overall binary size by up to 2%.
  3. retain/release calls are smaller: automatically inserted by ARCobjc_retain / objc_releaseReduced from 8 bytes to 4 bytes (ARM64), saving another 2% of the size.
  4. autorelease elision is faster and smaller: Use pointer comparison instead of instruction comparison during runtime, which not only improves speed but also saves the code size of marked instructions.

Detailed Content

Protocol checking: from startup calculation to precomputation

01:23

Swift’s protocol checking requires metadata support. Some metadata can be determined at compile time, but generic related ones can only be built at startup.

protocol CustomLoggable {
    var customLogString: String { get }
}

struct Event: CustomLoggable {
    let name: String
    let date: Date
    
    var customLogString: String {
        "\(name) at \(date)"
    }
}

func log(_ value: Any) {
    if let loggable = value as? CustomLoggable {
        print(loggable.customLogString)
    }
}

let event = Event(name: "WWDC", date: Date())
log(event)  // Performs the as? CustomLoggable check at runtime

Key points:

  • as? CustomLoggableWhen the compile time cannot be completely determined, it will be handled by the runtime.
  • The runtime relies on protocol checking metadata to determine whether the object conforms to the protocol
  • In a generic scenario, a large amount of metadata can only be constructed at startup, which can take hundreds of milliseconds.
  • The Swift runtime of iOS 16 advances these calculations into the dyld closure, and reads the results directly at startup

Message Send: selector stub shared code

03:10

Every method call in Objective-C eventually becomesobjc_msgSend. On ARM64, a complete call requires 12 bytes: 4 bytes calling instruction + 8 bytes preparing selector.

// This code produces many objc_msgSend calls
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [[NSDateComponents alloc] init];
components.year = 2022;
components.month = 6;
components.day = 6;
NSDate *theDate = [cal dateFromComponents:components];

Key points:

  • eachobjc_msgSendIn the call, 8 bytes are used to prepare the selector
  • The preparation code for the same selector is exactly the same and can be extracted into a shared stub function
  • The Xcode 14 compiler and linker automatically do this optimization
  • Default mode, which balances size and performance, is also available-objc_stubs_smallLinker flags only optimize size

Retain/Release: Custom calling convention

06:57

ARC automatically insertedobjc_retainandobjc_releaseis an ordinary C function and must comply with the C calling convention. This means that the compiler has to generate additional instructions to put the object pointer into the correct register.

Apple designed a custom calling convention for retain/release. The compiler selects the most appropriate calling variant based on the current location of the object pointer, eliminating unnecessarymoveinstruction.

Key points:

  • objc_retain / objc_releaseReduced from 8 bytes to 4 bytes on ARM64
  • Requires runtime support, so set the deployment target to iOS 16 / tvOS 16 / watchOS 9
  • Overlaid with message send optimization, the overall volume gain is more obvious

Autorelease Elision: pointer comparison instead of instruction comparison

09:26

When a function returns a temporary object, ARC uses autorelease to delay the release. If the caller retains immediately, the pair of autorelease/retain can cancel each other out, which is autorelease elision.

In the old implementation, the runtime loads code instructions as data and compares them with a special tag value to determine whether it can be elided. This operation is not efficient on the CPU.

The new implementation uses pointer comparison instead: 1.autoreleaseSave the return address pointer when 2.retainGet the current return address pointer when 3. If the two pointers are the same, it means they are matching autorelease/retain pairs and can be elided

Key points:

  • Pointer comparison is much faster than loading instructions as data
  • No special marking instructions are required, and the code size is further reduced
  • Automatically takes effect when iOS 16 is running; code volume gains require the deployment target to be upgraded to iOS 16

Core Takeaways

  • Recompile existing apps now Recompile with Xcode 14 without changing the code, and the app size can be reduced by about 2%. This is a zero cost benefit and should be implemented as soon as possible.

  • Upgrade deployment target to iOS 16 After the upgrade, retain/release optimization and smaller autorelease elision sequence save about 2% of the volume. After assessing user device distribution, you can consider moving forward with this upgrade.

  • Check the proportion of protocol checks in startup time If your app starts slowly, use Instruments’ Time Profiler to see if there are a lot of protocol checks during the startup phase. This issue will be automatically mitigated on iOS 16, but it’s also possible to look at your code for patterns of overuse of protocol checks.

  • Use in volume sensitive applications-objc_stubs_small If App size is a core indicator (such as watchOS App), you can add it in the linker settings.-objc_stubs_small, pushing the size optimization of message send stub to the maximum.

Comments

GitHub Issues · utterances