I have set the Swift compiler flag -warn-long-function-bodies
to 90 milliseconds, to see which functions in my project are taking too long to compile (due to type checking).
I have the following method:
func someKey(_ sectionType: SectionType, row: Int) -> String { let suffix = row == 0 ? "top" : "content" return "\(sectionType)_\(suffix)" }
(SectionType
is an String-backed enum)
As it is above, it takes 96ms on a 2017 MacBook Pro. The first thing I tried is to circumvent string interpolation and use \(sectionType.rawValue)
instead of \(sectionType)
, but now it gives me 106 ms. Wrong move...
Next, I changed:
let suffix = row == 0 ? "top" : "content"
to:
let suffix = "top"
The warning goes away, so it is the ternary operator that is causing trouble.
I tried this instead:
let suffix: String = { // Note the type annotation! if row == 0 { return "top" } return "content" }()
...but now it is the closure that takes 97 ms (the whole function, 101).
I even tried the more explicit:
let suffix: String = { if row == 0 { return String("top") } else { return String("content") } }()
...and I get closure: 94ms; function: 98ms.
What's going on?
Is my 90-milliseconds limit too low? I know there was (is?) a type-checking bug with dictionary literals, but this seems something entirely different...?
My environment is Xcode 8.3.2 (8E2002), Swift: Apple Swift version 3.1 (swiftlang-802.0.53 clang-802.0.42)
But Wait! There's more...
I tried this function body:
func someKey(_ sectionType: SectionType, row: Int) -> String { if row == 0 { return "\(sectionType.rawValue)_top" } else { return "\(sectionType.rawValue)_content" } }
...and it takes 97ms~112ms!?
Addendum: I transplanted the function and enum to a clean, minimal project (Single View Application) set up the same warning but it does not happen. I'm sure the project as a whole is affecting this one method somehow, but can't quite figure how yet...
Addendum 2: I tested the static version of my function: use fixed suffix "top" regardless of the value of row
(this takes less than 90 ms and triggers no warning), but added the following if
block:
func someKey(_ sectionType: SectionType, row: Int) -> String { if row == 0 { print("zero") } else { print("non-zero") } let suffix: String = "top" return "\(sectionType)_\(suffix)" }
This takes me back to 96~98 ms! So the problem arises when comparing row to zero?
Workaround: I kept playing with my code and somehow discovered that if I replace the if
block with a switch
statement, the issue goes away:
func someKey(_ sectionType: SectionType, row: Int) -> String { let suffix: String = { switch row { case 0: return "top" default: return "content" } }() return "\(sectionType)_\(suffix)" }
(I won't answer my own question because I do not consider this an explanation of what's really going on)
Compile time type checking (or static type checking) operates on the Swift source code. The Swift compiler looks at explicitly stated and inferred types and ensures correctness of our type constraints. Here’s a trivial example of static type checking:
In order to check if an object is of given type in Swift, you can use the type check operator is. Given an example class Item , you can check if the object is of its type like that: Same applies to built-in data types in Swift. For example, if you’d like to check if given object is a String or Int:
This is a type inference problem. The Swift compiler doesn’t know what type is coming next so it has to investigate and find out before it can continue compilation. This case is a particular “quirk” where adding more data increases compile time exponentially but fundamentally Swift is doing The Right Thing: checking which type it thinks you mean.
Static type checking occurs at compile time and dynamic type checking happens at run time. Each of these two stages come with a different, partially incompatible, toolset. Compile time type checking (or static type checking) operates on the Swift source code.
I think it's the ternary operator.
I had similar results in Xcode 11 (~93ms) but the compile time decreases to ~23ms with:
func someKey(_ sectionType: SectionType, row: Int) -> String { var suffix = "top" if row != 0 { suffix = "content" } return "\(sectionType)_\(suffix)" }
By changing the logic on this line, I think we can prove it's the ternary logic because the method reduces to ~1ms. I've just made row a Boolean.
func someKey(_ sectionType: SectionType, row: Bool) -> String { let suffix = row ? "top" : "content" return "\(sectionType)_\(suffix)" }
Equally (no pun intended) changing the ternary logic to let suffix = row != 0 ? "top" : "content"
halves the compile time. Which is comparable to my first code block. !=
is faster for Swift to understand than ==
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With