Swift Compiler: Boosting Efficiency

Saeid Rezaeisadrabadi
6 min readJan 2, 2024

--

In this story, I want to talk about the Swift compiler again! Yes.
After I published this story, Impact of Type Inference on Swift Compile Time: A Comparative Story, senior iOS developer Lucas van Dongen sent a message to me on Linkedin and we talked about it, he was surprised by the result. and he started to do the benchmarking on his own and got different results.

You can check his article about swift compiler performance here and do the benchmarking on your own. you can clone his repository and do it yourself. please create a new issue like me if you face different results too.
This was the back story; now, I’ll delve into the compile time of a more complex struct from his repository. BareComputedContainer.

I measured compile time on this file and the result shocked me.
It took around 40 seconds to compile.
I thought that maybe I could reduce the compile time by a little optimization on this file. and I thought maybe other developers also wanted to do it. so I want to share the steps that I took to reduce it to around 0.3 seconds.

Generated with AI

Optimization

This is a sample from the file:

import Foundation

struct BareComputedContainer1 {
let id = 1
let cityName: String? = "Amsterdam"
let price: Price? = .init(currency: "EUR", public: nil, charged: 1)
let ufi: NSString = .init(string: "ufi")
let offers: [Offer] = .init()

var trackingProduct: TravelProduct {
.init(
id: id,
items: offers,
destination: .init(
type: "ufi",
name: cityName ?? "",
id: String(ufi)
),
price: .init(
currency: price?.currency ?? "",
value: price?.public ?? price?.charged ?? 0
)
)
}
}

Step 1

Initially, I consolidated all dependencies into the same file to eliminate linking effects.
Then, I thought that the Foundation import is not necessary, also that NSString is not the Swift native type. It’s better to use the Swift native types whenever possible. The language is optimized to use them. with this change, I can also remove the type-casting id: String(ufi).

With these changes, the compile time was reduced to approximately 13 seconds.

struct BareComputedContainer1 {
let id = 1
let cityName: String? = "Amsterdam"
let price: Price? = .init(currency: "EUR", public: nil, charged: 1)
let ufi = "ufi"
let offers: [Offer] = .init()

var trackingProduct: TravelProduct {
.init(
id: id,
items: offers,
destination: .init(
type: "ufi",
name: cityName ?? "",
id: ufi
),
price: .init(
currency: price?.currency ?? "",
value: price?.public ?? price?.charged ?? 0
)
)
}
}

Step 2

As I mentioned in this story, it’s better not to use.init() for native type, I replaced it with []in this line. let offers: [Offer] = .init().

After this change, I was able to save 300 ms. it’s not big but it’s better to keep it in mind and don’t use .init() for native types.

struct BareComputedContainer1 {
let id = 1
let cityName: String? = "Amsterdam"
let price: Price? = .init(currency: "EUR", public: nil, charged: 1)
let ufi = "ufi"
let offers: [Offer] = []

var trackingProduct: TravelProduct {
.init(
id: id,
items: offers,
destination: .init(
type: "ufi",
name: cityName ?? "",
id: ufi
),
price: .init(
currency: price?.currency ?? "",
value: price?.public ?? price?.charged ?? 0
)
)
}
}

Step 3

I can remove the optional from cityName because it’s not optional in our test. and I can even remove the explicit type.
From now on, not all improvements may be feasible in real projects due to business logic or UI design constraints. but it is worth seeing how can we save time.

With this change, I was able to save ~ 4.7 seconds.

struct BareComputedContainer1 {
let id = 1
let cityName = "Amsterdam"
let price: Price? = .init(currency: "EUR", public: nil, charged: 1)
let ufi = "ufi"
let offers: [Offer] = []

var trackingProduct: TravelProduct {
.init(
id: id,
items: offers,
destination: .init(
type: "ufi",
name: cityName ?? "",
id: ufi
),
price: .init(
currency: price?.currency ?? "",
value: price?.public ?? price?.charged ?? 0
)
)
}
}

Step 4

Now I can also remove Nil-Coalescing operator from this line name: cityName ?? “”, because cityName is not optional anymore.

With the last change, I was able to save ~ 3.2 seconds.

struct BareComputedContainer1 {
let id = 1
let cityName = "Amsterdam"
let price: Price? = .init(currency: "EUR", public: nil, charged: 1)
let ufi = "ufi"
let offers: [Offer] = []

var trackingProduct: TravelProduct {
.init(
id: id,
items: offers,
destination: .init(
type: "ufi",
name: cityName,
id: ufi
),
price: .init(
currency: price?.currency ?? "",
value: price?.public ?? price?.charged ?? 0
)
)
}
}

Step 5

If I remove optional from this line let price: Price?, I’m able to make this line simpler for the compiler and save ~ 3.5 seconds.

Here is the final code

struct BareComputedContainer1 {
let id = 1
let cityName = "Amsterdam"
let price: Price = .init(currency: "EUR", public: nil, charged: 1)
let ufi = "ufi"
let offers: [Offer] = []

var trackingProduct: TravelProduct {
.init(
id: id,
items: offers,
destination: .init(
type: "ufi",
name: cityName,
id: ufi
),
price: .init(
currency: price.currency,
value: price.public ?? price.charged ?? 0
)
)
}
}

and a chart to make it more tangible.

With 5 small optimizations, I reduced the compile time by approximately 39 seconds, a substantial improvement.

Compare Bare init with Explicit

I saved a lot of time on compile time, now it’s time to compare the final version of the Bare initializer with the Explicit initializer.

Explicit initializer was ~ 100 ms faster than Bare initializer on complex struct. which I was not expecting this result. because Bare init is faster on simple struct(based on my benchmark) but on complex struct, it’s different.

Another surprising result is that I benchmarked the file without any optimization; I only changed the initializer to the Explicit one.

struct BareComputedContainer1 {
let id = 1
let cityName: String? = "Amsterdam"
let price: Price? = Price(currency: "EUR", public: nil, charged: 1)
let ufi: NSString = .init(string: "ufi")
let offers: [Offer] = .init()

var trackingProduct: TravelProduct {
TravelProduct(
id: id,
items: offers,
destination: Destination(
type: "ufi",
name: cityName ?? "",
id: String(ufi)
),
price: TravelPrice(
currency: price?.currency ?? "",
value: price?.public ?? price?.charged ?? 0
)
)
}
}

and it took ~ 677 ms to compile. It means without any optimization, it is ~ 38 seconds faster than Bare init.

Conclusion

While compile time might not pose a significant concern for most companies since it primarily occurs on CI machines and isn’t a blocker for devs or businesses, as a developer, I think focusing on the language details and the compiler is beneficial, Attention to detail enhances code quality and the final product. I’ll endeavor to delve further into this area.
If you have a different result or an idea, please don’t hesitate to reach out to me on LinkedIn or comment on this story.

--

--

Saeid Rezaeisadrabadi
Saeid Rezaeisadrabadi

Written by Saeid Rezaeisadrabadi

Over 8 years of experience in iOS software development

Responses (1)