gahms
(Nicolai Henriksen)
1
I have this Equatable implementation:
static func == (lhs: DayViewModel, rhs: DayViewModel) -> Bool {
lhs.id == rhs.id
&& lhs.referenceDate == rhs.referenceDate
&& lhs.dayOfMonth == rhs.dayOfMonth
&& lhs.dayOfWeekMedium == rhs.dayOfWeekMedium
&& lhs.dayOfWeek == rhs.dayOfWeek
&& lhs.isToday == rhs.isToday
&& lhs.isWeekend == rhs.isWeekend
&& lhs.isFirstDayOfWeek == rhs.isFirstDayOfWeek
&& lhs.weekNumber == rhs.weekNumber
&& lhs.year == rhs.year
&& lhs.dayOfYear == rhs.dayOfYear
&& lhs.events == rhs.events
&& lhs.birthdays == rhs.birthdays
&& lhs.anniversaries == rhs.anniversaries
&& lhs.lunarEvent == rhs.lunarEvent
&& lhs.marginMark == rhs.marginMark
&& lhs.isHoliday == rhs.isHoliday
&& lhs.astroDayViewModel == rhs.astroDayViewModel
}
It takes 200+ ms to type check.
Some if the properties are Optional, some are arrays. What is the best approach to get the compile time down while not completely destroying the runtime performance?
Approach A
I have tried splitting into several separate statements but even with only 4 comparisons in each, the compile time is still above 100 ms.
static func == (lhs: DayViewModel, rhs: DayViewModel) -> Bool {
let a = lhs.id == rhs.id
&& lhs.referenceDate == rhs.referenceDate
&& lhs.dayOfMonth == rhs.dayOfMonth
&& lhs.dayOfWeekMedium == rhs.dayOfWeekMedium
let b = lhs.dayOfWeek == rhs.dayOfWeek
&& lhs.isToday == rhs.isToday
&& lhs.isWeekend == rhs.isWeekend
&& lhs.isFirstDayOfWeek == rhs.isFirstDayOfWeek
...
return a && b && c && d && e
}
Aproach B
static func == (lhs: DayViewModel, rhs: DayViewModel) -> Bool {
guard lhs.id == rhs.id else { return false }
guard lhs.referenceDate == rhs.referenceDate else { return false }
...
return true
}
which seems ok readable.
Approach C
static func == (lhs: DayViewModel, rhs: DayViewModel) -> Bool {
let b: [Bool] = [
lhs.id.rawValue == rhs.id.rawValue,
lhs.referenceDate == rhs.referenceDate,
...
]
return b.allSatisfy(\.self)
}
tera
2
How much is the overall compilation slowdown with Option A and Option B?
Definitely don't use Option C, it's an extra memory allocation and besides it doesn't short-circuit evaluation and calculates all == first.
Jumhyn
(Frederick Kellison-Linn)
3
I'm assuming the underlying model here is a class, or else there's some other reason you can't rely on the synthesized Equatable implementation. So another option would be to factor out the equality-relevant state into a separate struct and then rely on Equatable synthesis.
2 Likes
This seems like something we shouldn't have any trouble type-checking quickly. Can you make this a standalone test case and share it with us?
1 Like
tera
5
+1
Yet another approach would be making a tuple:
(lhs.a, lhs.b, ...) == (rhs.a, rhs.b, ...)
but it's probably not as fast as Option B.
Jumhyn
(Frederick Kellison-Linn)
6
It also won't work if you have more than six properties to compare.
1 Like
tera
7
FWIW, the way it is stated now "the following example compares tuples made up of 6 components" doesn't look a limit, just an example.
gahms
(Nicolai Henriksen)
8
Good to hear. I will make the most minimal example that still reproduces it and share it.
1 Like
gahms
(Nicolai Henriksen)
9
I have tried to isolate the slow func == and I do not understand the results.
I made a new Xcode project and copied the DayViewModel class to it. I also copied the structs referenced as properties - 1 final class, 4 structs, 4 enums total involved.
So I would expect a result very similar to my full project. In the full project Xcode reports ~ 240-250 ms for the func == . In the clean project 120-130 ms, both clean builds. I am surprised that the clean project has the same function so much faster type checked.
The class and one of the structs had SwiftUI Color properties. I changed the type of these to String and removed the SwiftUI imports. The reproduction project is now down to ~60 ms for the func ==. Again I am surprised that this change makes such a difference.
If I begin to remove any property the time drops for each one and I cannot seem to locate any single that makes a big difference.
The results are the same for Xcode 15.4 and 16.0 beta 3 (16A5202i)
Do you think this is isolated enough to be of use for you if I submit it?
gahms
(Nicolai Henriksen)
10
This variant will not compile.
Binary operator '==' cannot be applied to two '(DayViewModel.SID, Date, String, String, String, Bool, Bool, Bool, Int, Int, DateComponents, [EventViewModel], [AnniversaryViewModel], [AnniversaryViewModel], LunarEvent?, Color?, Bool, AstroDayViewModel)' (aka '(Identifier<DayViewModel>, Date, String, String, String, Bool, Bool, Bool, Int, Int, DateComponents, Array<EventViewModel>, Array<AnniversaryViewModel>, Array<AnniversaryViewModel>, Optional<LunarEvent>, Optional<Color>, Bool, AstroDayViewModel)') operands
Jon_Shier
(Jon Shier)
11
Type inference is largely lazy in the compiler, so when you see large time values like this, it's often due to the compiler doing a bunch of necessary but one off work. If you create a second type with all the same properties, does it also show a time warning? I'd expect not, or at a much lower time.
2 Likes
vanvoorden
(Rick van Voorden)
12
It looks like the original "6D" limitation was motivated by the impact the codegen would have on codesize.
It is possible that parameter packs might enable Swift to ship these all with "just one" function (generic across packs).
1 Like
vanvoorden
(Rick van Voorden)
13
Just brainstorming here… by any chance did DayViewModel (in the original module) have dependencies on public types that were located in other modules?
xwu
(Xiaodi Wu)
15
The codegen is currently suboptimal, so we can’t get rid of concrete overloads yet, but it does work.
1 Like
gahms
(Nicolai Henriksen)
16
If I make a new class with exact same properties and func == implementation, it take the same time to type check as the original and the original still takes the same time.